aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/__init__.py0
-rw-r--r--test/config/__init__.py0
-rw-r--r--test/config/test_schema.py284
-rw-r--r--test/config/test_settings.py903
-rw-r--r--test/config/test_types.py251
5 files changed, 1438 insertions, 0 deletions
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/__init__.py
diff --git a/test/config/__init__.py b/test/config/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/config/__init__.py
diff --git a/test/config/test_schema.py b/test/config/test_schema.py
new file mode 100644
index 0000000..9e3d3b7
--- /dev/null
+++ b/test/config/test_schema.py
@@ -0,0 +1,284 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# standard imports
+import unittest
+
+# tagit imports
+from tagit.config import types
+from tagit.utils import errors
+
+# objects to test
+from tagit.config.schema import ConfigSchema, ConfigKey, ConfigTitle, IncompatibleTypes
+
+
+## code ##
+
+def _config_title_check(self, key0, key1):
+ self.assertEqual(key0, key1)
+ self.assertEqual(key0._title, key1._title)
+ self.assertEqual(key0._description, key1._description)
+ self.assertSetEqual(key0._modules, key1._modules)
+
+def _config_key_check(self, key0, key1):
+ self.assertEqual(key0, key1)
+ self.assertEqual(key0._title, key1._title)
+ self.assertEqual(key0._description, key1._description)
+ self.assertSetEqual(key0._modules, key1._modules)
+ self.assertSetEqual(key0._examples, key1._examples)
+
+class TestConfigSchema(unittest.TestCase):
+ def test_collection(self):
+ # getitem, call
+ # contains
+ # iter
+ # len
+ # keys
+ pass
+
+ def test_title(self):
+ schema = ConfigSchema()
+ # recognize title
+ schema.declare_title(('some', 'key'), 'module', 'title', 'description')
+ schema.declare(('other', 'key'), types.Int(), 0,
+ 'module', 'title', 'description', 'example')
+ self.assertTrue(schema.is_title(('some', 'key')))
+ self.assertFalse(schema.is_title(('other', 'key')))
+ _config_title_check(self, schema.get_title(('some', 'key')),
+ ConfigTitle(('some', 'key'), 'title', 'module', 'description'))
+ self.assertRaises(KeyError, schema.get_title, ('other', 'key'))
+ # config takes precedence
+ schema.declare(('some', 'key'), types.Int(), 0,
+ 'module', 'title', 'description', 'example')
+ self.assertFalse(schema.is_title(('some', 'key')))
+ # can still retrieve the title
+ _config_title_check(self, schema.get_title(('some', 'key')),
+ ConfigTitle(('some', 'key'), 'title', 'module', 'description'))
+
+ def test_declare(self):
+ schema = ConfigSchema()
+ # keys can be declared
+ schema.declare(('some', 'key'), types.Int(), 0,
+ 'module', 'title', 'description', 'example')
+ self.assertSetEqual(set(schema.config.keys()), {('some', 'key')})
+ _config_key_check(self, schema.config[('some', 'key')],
+ ConfigKey(('some', 'key'), types.Int(), 0,
+ 'module', 'title', 'description', 'example'))
+ # double insert with identical signature is accepted
+ schema.declare(('some', 'key'), types.Int(), 0,
+ 'module', 'title', 'description', 'example')
+ self.assertSetEqual(set(schema.config.keys()), {('some', 'key')})
+ _config_key_check(self, schema.config[('some', 'key')],
+ ConfigKey(('some', 'key'), types.Int(), 0,
+ 'module', 'title', 'description', 'example'))
+ # additional info is accepted
+ schema.declare(('some', 'key'), types.Int(), 0,
+ 'other_module', 'other_title', 'other_description', 'other_example')
+ self.assertSetEqual(set(schema.config.keys()), {('some', 'key')})
+ ck = ConfigKey(('some', 'key'), types.Int(), 0,
+ 'module', 'other_title', 'other_description', 'example')
+ ck._examples.add('other_example')
+ ck._modules.add('other_module')
+ _config_key_check(self, schema.config[('some', 'key')], ck)
+
+ # empty key is rejected
+ self.assertRaises(errors.ProgrammingError, schema.declare, [], types.Int(), 0)
+ # empty type is rejected
+ self.assertRaises(AttributeError, schema.declare, ('foo', ), None, None)
+ # invalid defaults are rejected
+ self.assertRaises(errors.ProgrammingError, schema.declare, ('foo', ), types.Unsigned(), -1)
+ self.assertRaises(errors.ProgrammingError, schema.declare, ('foo', ), types.Int(), 'abc')
+ self.assertRaises(errors.ProgrammingError, schema.declare, ('foo', ), types.String(), 123)
+ self.assertRaises(errors.ProgrammingError, schema.declare, ('foo', ), types.Enum('foo'), 'bar')
+ # double insert with different signature is rejected
+ self.assertRaises(IncompatibleTypes, schema.declare, ('some', 'key'), types.Unsigned(), 0)
+ self.assertRaises(IncompatibleTypes, schema.declare, ('some', 'key'), types.Int(), 2)
+
+ def test_declare_title(self):
+ schema = ConfigSchema()
+ # titles can be declared
+ schema.declare_title(('some', 'key'), 'module', 'title', 'description')
+ self.assertSetEqual(set(schema.titles.keys()), {('some', 'key')})
+ _config_title_check(self, schema.titles[('some', 'key')],
+ ConfigTitle(('some', 'key'),
+ 'title', 'module', 'description'))
+ # double insert is accepted
+ schema.declare_title(('some', 'key'), 'module', 'title', 'description')
+ self.assertSetEqual(set(schema.titles.keys()), {('some', 'key')})
+ _config_title_check(self, schema.titles[('some', 'key')], ConfigTitle(('some', 'key'),
+ 'title', 'module', 'description'))
+ # additional info is accepted
+ schema.declare_title(('some', 'key'), 'other_module', 'other_title', 'other_description')
+ self.assertSetEqual(set(schema.titles.keys()), {('some', 'key')})
+ ck = ConfigTitle(('some', 'key'), 'other_title', 'module', 'other_description')
+ ck._modules.add('other_module')
+ _config_title_check(self, schema.titles[('some', 'key')], ck)
+ # empty key is rejected
+ self.assertRaises(errors.ProgrammingError, schema.declare_title, [], types.Int(), 0)
+ # title and config key can exist in parallel
+ schema.declare(('other', 'key'), types.Int(), 0)
+ self.assertSetEqual(set(schema.config.keys()), {('other', 'key')})
+ _config_key_check(self, schema.config[('other', 'key')],
+ ConfigKey(('other', 'key'), types.Int(), 0))
+ schema.declare_title(('other', 'key'), 'module', 'title', 'description')
+ self.assertIn(('other', 'key'), set(schema.titles.keys()))
+ _config_title_check(self, schema.titles[('other', 'key')], ConfigTitle(('other', 'key'),
+ 'title', 'module', 'description'))
+
+
+class TestConfigTitle(unittest.TestCase):
+ def test_magicks(self):
+ ck = ConfigTitle(('some', 'key'), 'title', 'module', 'description')
+ # representation
+ self.assertEqual(repr(ck), "ConfigTitle(('some', 'key'), title)")
+ # comparison
+ self.assertEqual(ck, ConfigTitle(('some', 'key')))
+ self.assertEqual(hash(ck), hash(ConfigTitle(('some', 'key'))))
+ self.assertNotEqual(ck, ConfigTitle(('other', 'key')))
+
+ def test_properties(self):
+ ck = ConfigTitle(('some', 'key'), 'title', 'module', 'some_description')
+ # key can't be overwritten
+ self.assertRaises(AttributeError, ck.__setattr__, 'key', ('other', 'key'))
+ # modules
+ ck.modules = 'other_module'
+ self.assertSetEqual(ck.modules, {'module', 'other_module'})
+ ck.modules = None
+ self.assertSetEqual(ck.modules, {'module', 'other_module'})
+ ck.modules = ''
+ self.assertSetEqual(ck.modules, {'module', 'other_module'})
+ # title
+ ck.title = 'other_title'
+ self.assertEqual(ck.title, 'other_title')
+ ck.title = None
+ self.assertEqual(ck.title, 'other_title')
+ ck.title = ''
+ self.assertEqual(ck.title, 'other_title')
+ # description
+ ck.description = 'other_description'
+ self.assertEqual(ck.description, 'other_description')
+ ck.description = None
+ self.assertEqual(ck.description, 'other_description')
+ ck.description = ''
+ self.assertEqual(ck.description, 'other_description')
+
+ def test_tree(self):
+ self.assertEqual(ConfigTitle(('some', 'path', 'to', 'a', 'key')).branch,
+ ('some', 'path', 'to', 'a'))
+ self.assertEqual(ConfigTitle(('some', 'path', 'to', 'a', 'key')).leaf,
+ 'key')
+ self.assertEqual(ConfigTitle(('somekey', )).branch,
+ tuple())
+ self.assertEqual(ConfigTitle(('somekey', )).leaf,
+ 'somekey')
+
+
+class TestConfigKey(unittest.TestCase):
+ def test_magicks(self):
+ ck = ConfigKey(('some', 'key'), types.Int(), 0,
+ 'module', 'title', 'some_description', 'some_example')
+ ck.example = 'other_example'
+ ck.modules = 'other_module'
+ # representation
+ self.assertEqual(repr(ck), "ConfigKey(('some', 'key'), Int, 0)")
+ # comparison
+ self.assertEqual(ck, ConfigKey(('some', 'key'), types.Int(), 0))
+ self.assertEqual(hash(ck), hash(ConfigKey(('some', 'key'), types.Int(), 0)))
+ self.assertNotEqual(ck, ConfigKey(('some', 'key'), types.Int(), 1))
+ self.assertNotEqual(ck, ConfigKey(('some', 'key'), types.Unsigned(), 0))
+ self.assertNotEqual(ck, ConfigKey(('other', 'key'), types.Int(), 0))
+
+ def test_properties(self):
+ ck = ConfigKey(('some', 'key'), types.Int(), 0,
+ 'module', 'title', 'some_description', 'some_example')
+ self.assertEqual(ck.key, ('some', 'key'))
+ self.assertEqual(ck.type, types.Int())
+ self.assertEqual(ck.default, 0)
+ self.assertSetEqual(ck.modules, {'module'})
+ self.assertEqual(ck.title, 'title')
+ self.assertEqual(ck.description, 'some_description')
+ self.assertEqual(ck.example, 'some_example')
+
+ # key, type, default can't be overwritten
+ self.assertRaises(AttributeError, ck.__setattr__, 'key', ('other', 'key'))
+ self.assertRaises(AttributeError, ck.__setattr__, 'type', types.Unsigned())
+ self.assertRaises(AttributeError, ck.__setattr__, 'default', 123)
+ # modules
+ ck.modules = 'other_module'
+ self.assertSetEqual(ck.modules, {'module', 'other_module'})
+ ck.modules = None
+ self.assertSetEqual(ck.modules, {'module', 'other_module'})
+ ck.modules = ''
+ self.assertSetEqual(ck.modules, {'module', 'other_module'})
+ # title
+ ck.title = 'other_title'
+ self.assertEqual(ck.title, 'other_title')
+ ck.title = None
+ self.assertEqual(ck.title, 'other_title')
+ ck.title = ''
+ self.assertEqual(ck.title, 'other_title')
+ # description
+ ck.description = 'other_description'
+ self.assertEqual(ck.description, 'other_description')
+ ck.description = None
+ self.assertEqual(ck.description, 'other_description')
+ ck.description = ''
+ self.assertEqual(ck.description, 'other_description')
+ # example
+ ck.example = 'other_example'
+ ex = ck.example
+ self.assertTrue('other_example' in ex)
+ self.assertTrue('some_example' in ex)
+ ck.example = None
+ self.assertEqual(ck.example, ex)
+ ck.example = ''
+ self.assertEqual(ck.example, ex)
+ # type example if unspecified
+ self.assertEqual(ConfigKey(('some', 'key'), types.Int(), 0).example, types.Int().example)
+
+ def test_checks(self):
+ # check
+ self.assertTrue(ConfigKey(('some', 'key'), types.Int(), 0).check(0))
+ self.assertFalse(ConfigKey(('some', 'key'), types.Int(), 0).check(1.23))
+ self.assertFalse(ConfigKey(('some', 'key'), types.Int(), 0).check('foobar'))
+ self.assertTrue(ConfigKey(('some', 'key'), types.Unsigned(), 0).check(0))
+ self.assertFalse(ConfigKey(('some', 'key'), types.Unsigned(), 0).check(-1))
+ self.assertTrue(ConfigKey(('some', 'key'), types.String(), 0).check('foobar'))
+ self.assertFalse(ConfigKey(('some', 'key'), types.String(), 0).check(['foo', 'bar']))
+
+ # backtrack
+ self.assertTrue(ConfigKey(['somekey'], types.Int(), 0).backtrack(0))
+ self.assertTrue(ConfigKey(['somekey'], types.Int(), 0).backtrack(-1))
+ self.assertTrue(ConfigKey(['somekey'], types.Int(), 0).backtrack(123))
+ self.assertRaises(types.ConfigTypeError,
+ ConfigKey(['somekey'], types.Int(), 0).backtrack, 1.23)
+ self.assertRaises(types.ConfigTypeError,
+ ConfigKey(['somekey'], types.Int(), 0).backtrack, 'foobar')
+ self.assertTrue(ConfigKey(['somekey'], types.Unsigned(), 0).backtrack(0))
+ self.assertRaises(types.ConfigTypeError,
+ ConfigKey(['somekey'], types.Unsigned(), 0).backtrack, -1)
+ self.assertTrue(ConfigKey(['somekey'], types.String(), 0).backtrack('foobar'))
+ self.assertRaises(types.ConfigTypeError,
+ ConfigKey(['somekey'], types.String(), 0).backtrack, ['foo', 'bar'])
+
+ def test_tree(self):
+ self.assertEqual(ConfigKey(('some', 'path', 'to', 'a', 'key'), types.Int(), 0).branch,
+ ('some', 'path', 'to', 'a'))
+ self.assertEqual(ConfigKey(('some', 'path', 'to', 'a', 'key'), types.Int(), 0).leaf,
+ 'key')
+ self.assertEqual(ConfigKey(('somekey', ), types.Int(), 0).branch,
+ tuple())
+ self.assertEqual(ConfigKey(('somekey', ), types.Int(), 0).leaf,
+ 'somekey')
+
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/config/test_settings.py b/test/config/test_settings.py
new file mode 100644
index 0000000..d7ce7f8
--- /dev/null
+++ b/test/config/test_settings.py
@@ -0,0 +1,903 @@
+"""Test settings loading and access.
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# standard imports
+import io
+import json
+import os
+import tempfile
+import unittest
+
+# tagit imports
+from tagit.config import Settings, ConfigError, types
+
+# objects to test
+from tagit.config.schema import ConfigSchema
+
+
+## code ##
+
+class TestSettings(unittest.TestCase):
+ def setUp(self):
+ # example schema
+ self.schema = ConfigSchema()
+ self.schema.declare(('ui', 'standalone', 'browser', 'cols'), types.Unsigned(), 3)
+ self.schema.declare(('session', 'paths', 'library'), types.Path(), '')
+ self.schema.declare(('session', 'paths', 'config'), types.Path(), '')
+ self.schema.declare(('extraction', 'constant', 'enabled'), types.Bool(), False)
+
+ # example config
+ self.cfg = Settings(schema=self.schema, data={
+ ('session', 'paths', 'library'): '/path/to/lib',
+ ('session', 'messaging', 'verbose'): 8,
+ })
+
+ def test_retrieval(self):
+ # get configured
+ self.assertEqual('/path/to/lib', self.cfg('session', 'paths', 'library'))
+ # get default
+ self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols'))
+ # get invalid
+ self.assertRaises(KeyError, self.cfg, 'ui', 'standalone', 'browser', 'rows')
+ # get unknown
+ self.assertEqual(8, self.cfg('session', 'messaging', 'verbose'))
+ self.assertRaises(KeyError, self.cfg, 'session', 'messaging', 'debug')
+ # get with default
+ self.assertEqual('/path/to/lib', self.cfg('session', 'paths', 'library', default='foo'))
+ self.assertEqual(8, self.cfg('session', 'messaging', 'verbose', default=12))
+ self.assertEqual(5, self.cfg('ui', 'standalone', 'browser', 'cols', default=5))
+ self.assertEqual(3.14, self.cfg('ui', 'standalone', 'browser', 'rows', default=3.14))
+ # get if invalid was configured
+ cfg = Settings(schema=self.schema, data={
+ ('session', 'paths', 'library'): 123,
+ ('session', 'messaging', 'verbose'): 8,
+ })
+ self.assertEqual(8, cfg('session', 'messaging', 'verbose'))
+ self.assertEqual('foobar', cfg('session', 'paths', 'library', default='foobar'))
+ self.assertEqual('', cfg('session', 'paths', 'library'))
+ # test aliases
+ self.assertEqual('/path/to/lib', self.cfg['session', 'paths', 'library'])
+ self.assertEqual('/path/to/lib', self.cfg[('session', 'paths', 'library')])
+
+ def test_view(self):
+ # retrieve view
+ sub = self.cfg('ui', 'standalone', 'browser')
+ self.assertListEqual(list(sub), [
+ ('cols', )])
+ # set in original affects view
+ self.assertEqual(3, sub('cols'))
+ self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 4)
+ self.assertEqual(4, sub('cols'))
+ # unset in original affects view
+ self.cfg.unset('ui', 'standalone', 'browser', 'cols')
+ self.assertEqual(3, sub('cols'))
+ # set in view affects original
+ sub.set(('cols', ), 5)
+ self.assertEqual(5, sub('cols'))
+ self.assertEqual(5, self.cfg('ui', 'standalone', 'browser', 'cols'))
+ # unset in view affects original
+ sub.unset('cols')
+ self.assertEqual(3, sub('cols'))
+ self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols'))
+ # has
+ self.assertIn(('cols', ), sub)
+ self.assertNotIn(('rows', ), sub)
+ self.assertNotIn(('ui', 'standalone', 'browser'), sub)
+ # unspecified view
+ sub = self.cfg('session', 'messaging')
+ self.assertListEqual(list(sub), [
+ ('verbose', )])
+ # upper branch view
+ sub = self.cfg('session')
+ self.assertCountEqual(list(sub), [
+ ('paths', 'library'),
+ ('paths', 'config'),
+ ('messaging', 'verbose')])
+ self.assertEqual('/path/to/lib', sub('paths', 'library'))
+ self.assertEqual(8, sub('messaging', 'verbose'))
+ self.assertRaises(KeyError, sub, 'session', 'paths', 'library')
+ # defaults
+ sub = self.cfg('ui', 'standalone')
+ self.assertCountEqual(list(sub), [
+ ('browser', 'cols')])
+ # aliases
+ sub = self.cfg['session']
+ self.assertCountEqual(list(sub), [
+ ('paths', 'library'),
+ ('paths', 'config'),
+ ('messaging', 'verbose')
+ ])
+
+ def test_modify_value(self):
+ # knowns
+ # overwrite default
+ self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 4)
+ self.assertEqual(4, self.cfg('ui', 'standalone', 'browser', 'cols'))
+ # overwrite configured
+ self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 5)
+ self.assertEqual(5, self.cfg('ui', 'standalone', 'browser', 'cols'))
+ # remove configured
+ self.cfg.unset('ui', 'standalone', 'browser', 'cols')
+ self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols'))
+ # remove default
+ self.cfg.unset('ui', 'standalone', 'browser', 'cols')
+ self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols'))
+ # overwrite with invalid
+ self.assertRaises(TypeError, self.cfg.set, ('ui', 'standalone', 'browser', 'cols'), 'hello')
+ # set to default
+ self.cfg.set(('session', 'paths', 'library'), '')
+ self.assertEqual('', self.cfg('session', 'paths', 'library'))
+ self.assertNotIn(('session', 'paths', 'library'), self.cfg.config)
+
+ # unknowns
+ # overwrite unknown
+ self.cfg.set(('session', 'messaging', 'verbose'), 1)
+ self.assertEqual(1, self.cfg('session', 'messaging', 'verbose'))
+ # overwrite unknonw with different type
+ self.cfg.set(('session', 'messaging', 'verbose'), 'hello')
+ self.assertEqual('hello', self.cfg('session', 'messaging', 'verbose'))
+ # remove unknown
+ self.cfg.unset('session', 'messaging', 'verbose')
+ self.assertRaises(KeyError, self.cfg, 'session', 'messaging', 'verbose')
+
+ # define new unknown
+ self.cfg.set(('storage', 'library', 'autosave'), 15)
+ self.assertEqual(15, self.cfg('storage', 'library', 'autosave'))
+ # overwrite new unknown
+ self.cfg.set(('storage', 'library', 'autosave'), 5)
+ self.assertEqual(5, self.cfg('storage', 'library', 'autosave'))
+ # overwrite new unknown with different type
+ self.cfg.set(('storage', 'library', 'autosave'), 'hello')
+ self.assertEqual('hello', self.cfg('storage', 'library', 'autosave'))
+ # remove unknown
+ self.cfg.unset('storage', 'library', 'autosave')
+ self.assertRaises(KeyError, self.cfg, 'storage', 'library', 'autosave')
+ # remove invalid
+ self.cfg.unset('storage', 'library', 'autosave')
+ self.assertRaises(KeyError, self.cfg, 'storage', 'library', 'autosave')
+
+ # alias
+ self.cfg['storage', 'library', 'autosave'] = 12
+ self.assertEqual(12, self.cfg('storage', 'library', 'autosave'))
+ del self.cfg['storage', 'library', 'autosave']
+ self.assertRaises(KeyError, self.cfg, 'storage', 'library', 'autosave')
+
+ def test_set_branch(self):
+ # knowns
+ cfg = Settings(schema=self.schema)
+ cfg.set(('session', ), {
+ 'paths': {'library': '/path/to/other'},
+ 'messaging': {'debug': True}})
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'library'): '/path/to/other',
+ ('session', 'messaging', 'debug'): True,
+ })
+ cfg.set(('session', 'paths'), {
+ 'library': '',
+ 'config': '/path/to/config',
+ 'numerical': '/path/to/num',
+ })
+ self.assertDictEqual(cfg.config, {
+ # library is omitted because it's the default
+ ('session', 'paths', 'config'): '/path/to/config',
+ ('session', 'paths', 'numerical'): '/path/to/num',
+ ('session', 'messaging', 'debug'): True, # keey rest
+ })
+ # error: value
+ self.assertRaises(TypeError, cfg.set, ('session', ), {
+ 'paths': {'library': 1234},
+ 'messaging': {'debug': True}
+ })
+ # error: subkey
+ self.assertRaises(TypeError, cfg.set, ('session', ), {
+ 'paths': 123,
+ 'messaging': {'debug': True}
+ })
+ # error: superkey
+ self.assertRaises(TypeError, cfg.set, ('session', ), {
+ 'paths': {'library': {'path': '/path/to/lib'}},
+ 'messaging': {'debug': True}
+ })
+
+ # unknowns
+ cfg = Settings(schema=self.schema)
+ cfg.set(('storage', ), {
+ 'library': {'autosave': 10, 'write_through': True},
+ 'index': {'preview_size': [10, 20, 30]}})
+ self.assertDictEqual(cfg.config, {
+ ('storage', 'library', 'autosave'): 10,
+ ('storage', 'library', 'write_through'): True,
+ ('storage', 'index', 'preview_size'): [10, 20, 30],
+ })
+ # unknowns with previous keys
+ cfg.set(('storage', ), {
+ 'library': {'autoindex': 20, 'autosave': 20},
+ 'numerical': {'write_through': False}})
+ self.assertDictEqual(cfg.config, {
+ ('storage', 'library', 'autosave'): 20, # change value
+ ('storage', 'library', 'autoindex'): 20, # add key
+ ('storage', 'library', 'write_through'): True, # keep key
+ ('storage', 'numerical', 'write_through'): False, # add key
+ ('storage', 'index', 'preview_size'): [10, 20, 30], # keep key
+ })
+ # error: superkey
+ self.assertRaises(TypeError, cfg.set, ('storage', ), {
+ 'library': {'autoindex': {'autosave': 20}},
+ 'numerical': {'write_through': True}})
+ # error: subkey
+ self.assertRaises(TypeError, cfg.set, ('storage', ), {
+ 'library': 123,
+ 'numerical': {'write_through': True}})
+
+ # alias
+ cfg['session'] = {
+ 'paths': {'library': '', 'numerical': 'otherpath'},
+ 'messaging': {'verbose': 3, 'debug': False}
+ }
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'numerical'): 'otherpath',
+ ('session', 'messaging', 'verbose'): 3,
+ ('session', 'messaging', 'debug'): False,
+ # keep the rest as-is
+ ('storage', 'library', 'autosave'): 20, # change value
+ ('storage', 'library', 'autoindex'): 20, # add key
+ ('storage', 'library', 'write_through'): True, # keep key
+ ('storage', 'numerical', 'write_through'): False, # add key
+ ('storage', 'index', 'preview_size'): [10, 20, 30], # keep key
+ })
+ cfg[('session', )] = {
+ 'paths': {'library': '/path/to/somewhere', 'numerical': 'mypath'},
+ 'messaging': {'debug': True}
+ }
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'library'): '/path/to/somewhere',
+ ('session', 'paths', 'numerical'): 'mypath',
+ ('session', 'messaging', 'debug'): True,
+ # keep the rest as-is
+ ('session', 'messaging', 'verbose'): 3,
+ ('storage', 'library', 'autosave'): 20, # change value
+ ('storage', 'library', 'autoindex'): 20, # add key
+ ('storage', 'library', 'write_through'): True, # keep key
+ ('storage', 'numerical', 'write_through'): False, # add key
+ ('storage', 'index', 'preview_size'): [10, 20, 30], # keep key
+ })
+
+ # test nesting
+ cfg['session']['paths'] = {'library': '/path/to/elsewhere'}
+ self.assertEqual('/path/to/elsewhere', cfg('session', 'paths', 'library'))
+ cfg['session', 'paths'] = {'library': '/path/to/elsewhere2'}
+ self.assertEqual('/path/to/elsewhere2', cfg('session', 'paths', 'library'))
+ cfg['session']['paths']['library'] = '/path/to/elsewhere3'
+ self.assertEqual('/path/to/elsewhere3', cfg('session', 'paths', 'library'))
+
+ def test_set_branch_dict(self):
+ # unknowns
+ cfg = Settings(schema=ConfigSchema(), data={
+ ('session', 'paths', 'preview', 'files'): 'thumbs'
+ })
+ # adding siblings is allowed
+ cfg.set(('session', 'paths', 'preview', 'original'), '')
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'preview', 'files'): 'thumbs',
+ ('session', 'paths', 'preview', 'original'): '',
+ })
+ # clearing sub-items is allowed
+ cfg.set(('session', 'paths', 'preview'), {})
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'preview'): {},
+ })
+ # adding sub-items is allowed
+ cfg.set(('session', 'paths', 'preview', 'sqlite'), 'thumbs.db')
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'preview', 'sqlite'): 'thumbs.db',
+ })
+ # adding siblings dict-style is allowed
+ cfg.set(('session', 'paths', 'preview'), {'exif': 'none', 'rest': {}})
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'preview', 'sqlite'): 'thumbs.db',
+ ('session', 'paths', 'preview', 'exif'): 'none',
+ ('session', 'paths', 'preview', 'rest'): {},
+ })
+ # adding sub-items to non-dict items is not allowed
+ self.assertRaises(TypeError, cfg.set, ('session', 'paths', 'preview', 'exif', 'sub'), 123)
+ # adding super-items of non-dict type is not allowed
+ self.assertRaises(TypeError, cfg.set, ('session', 'paths', 'preview'), 123)
+
+ def test_unset_branch(self):
+ # mixed knowns/unknowns
+ self.cfg.unset('session')
+ self.assertNotIn(('session', 'messaging', 'verbose'), self.cfg)
+ self.assertNotIn(('session', 'messaging'), self.cfg)
+ self.assertIn(('session', ), self.cfg)
+ self.assertEqual('', self.cfg('session', 'paths', 'library'))
+
+ # already empty
+ self.cfg.unset('ui', 'standalone')
+ self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols'))
+
+ def test_unset_branch_alias(self):
+ # mixed knowns/unknowns
+ del self.cfg['session']
+ self.assertNotIn(('session', 'messaging', 'verbose'), self.cfg)
+ self.assertNotIn(('session', 'messaging'), self.cfg)
+ self.assertIn(('session', ), self.cfg)
+ self.assertEqual('', self.cfg('session', 'paths', 'library'))
+
+ # already empty
+ del self.cfg['ui', 'standalone']
+ self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols'))
+
+ def test_has(self):
+ # check default
+ self.assertIn(('ui', 'standalone', 'browser', 'cols'), self.cfg)
+ # check configured
+ self.assertIn(('session', 'paths', 'library'), self.cfg)
+ # check newly configured
+ self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 4)
+ self.assertIn(('ui', 'standalone', 'browser', 'cols'), self.cfg)
+ # check invalid
+ self.assertNotIn(('ui', 'standalone', 'browser', 'rows'), self.cfg)
+ # check branch
+ self.assertIn(('ui', 'standalone', 'browser'), self.cfg)
+ # check branch
+ self.assertNotIn(('ui', 'standalone', 'filter'), self.cfg)
+
+ # check unknown
+ self.assertIn(('session', 'messaging', 'verbose'), self.cfg)
+ self.assertNotIn(('session', 'messaging', 'debug'), self.cfg)
+ # check unknown branch
+ self.assertIn(('session', 'messaging'), self.cfg)
+ self.assertNotIn(('storage', 'library'), self.cfg)
+ self.assertNotIn(('storage', ), self.cfg)
+
+ def test_iteration(self):
+ # length
+ self.assertEqual(5, len(self.cfg))
+ # iterate keys, explicit/implicit
+ self.assertCountEqual(list(self.cfg), [
+ ('ui', 'standalone', 'browser', 'cols'),
+ ('extraction', 'constant', 'enabled'),
+ ('session', 'paths', 'library'),
+ ('session', 'paths', 'config'),
+ ('session', 'messaging', 'verbose')])
+ self.assertCountEqual(list(self.cfg.keys()), [
+ ('ui', 'standalone', 'browser', 'cols'),
+ ('extraction', 'constant', 'enabled'),
+ ('session', 'paths', 'library'),
+ ('session', 'paths', 'config'),
+ ('session', 'messaging', 'verbose')])
+ # iterate items
+ self.assertCountEqual(list(self.cfg.items()), [
+ (('ui', 'standalone', 'browser', 'cols'), 3),
+ (('extraction', 'constant', 'enabled'), False),
+ (('session', 'paths', 'library'), '/path/to/lib'),
+ (('session', 'paths', 'config'), ''),
+ (('session', 'messaging', 'verbose'), 8)])
+
+ def test_magicks(self):
+ # comparison
+ self.assertEqual(self.cfg, self.cfg)
+ self.assertEqual(self.cfg, self.cfg())
+ self.assertNotEqual(self.cfg, self.cfg('ui'))
+ self.assertEqual(self.cfg, Settings(self.schema, data=self.cfg.config))
+ self.assertNotEqual(self.cfg, Settings(ConfigSchema(), data=self.cfg.config))
+ self.assertNotEqual(self.cfg, Settings(self.schema))
+ self.assertEqual(hash(self.cfg), hash(self.cfg))
+ self.assertEqual(hash(self.cfg), hash(self.cfg()))
+ self.assertEqual(hash(self.cfg), hash(Settings(self.schema, data=self.cfg.config)))
+ # representation
+ self.assertEqual(str(self.cfg), str(self.cfg.config))
+ self.assertEqual(repr(self.cfg), 'Settings(prefix=(), keys=2, len=5)')
+
+ def test_diff(self):
+ """
+ # diff
+ >>> cfg.unset('ui', 'standalone', 'browser', 'cols')
+ >>> cfg.diff(Settings())
+ Settings({'session': {'paths': {'library': '/path/to/lib'}}})
+ >>> cfg.set(('ui', 'standalone', 'browser', 'cols'), 4)
+ >>> cfg.diff(Settings())
+ Settings({'session': {'paths': {'library': '/path/to/lib'}},
+ 'ui': {'standalone': {'browser': {'cols': 4}}}})
+ """
+ # contains all configured
+ self.assertDictEqual(self.cfg.diff(Settings()).config, {
+ ('session', 'paths', 'library'): '/path/to/lib',
+ ('session', 'messaging', 'verbose'): 8,
+ })
+ # still no cols (because it won't be stored in self.cfg)
+ self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 3)
+ self.assertDictEqual(self.cfg.diff(Settings()).config, {
+ ('session', 'paths', 'library'): '/path/to/lib',
+ ('session', 'messaging', 'verbose'): 8,
+ })
+ # still no cols (because it's the default)
+ self.assertDictEqual(self.cfg.diff(Settings(data=
+ {('ui', 'standalone', 'browser', 'cols'): 3}
+ )).config, {
+ ('session', 'paths', 'library'): '/path/to/lib',
+ ('session', 'messaging', 'verbose'): 8,
+ })
+ # now with cols (because it deviates)
+ self.assertDictEqual(self.cfg.diff(Settings(data=
+ {('ui', 'standalone', 'browser', 'cols'): 5}
+ )).config, {
+ ('ui', 'standalone', 'browser', 'cols'): 3,
+ ('session', 'paths', 'library'): '/path/to/lib',
+ ('session', 'messaging', 'verbose'): 8,
+ })
+ # non-differing known
+ self.assertDictEqual(self.cfg.diff(Settings(data=
+ {('session', 'paths', 'library'): '/path/to/lib'}
+ )).config, {
+ ('session', 'messaging', 'verbose'): 8,
+ })
+ # non-differing unknown
+ self.assertDictEqual(self.cfg.diff(Settings(data=
+ {('session', 'messaging', 'verbose'): 8}
+ )).config, {
+ ('session', 'paths', 'library'): '/path/to/lib',
+ })
+
+ def test_flatten_tree(self):
+ schema = ConfigSchema()
+ schema.declare(('ui', 'standalone', 'tiledocks'),
+ types.Dict(types.String(), types.Dict(types.String(), types.Int())), {})
+ schema.declare(('session', 'debug'), types.Bool(), True)
+ schema.declare(('storage', 'library', 'autosave'), types.Unsigned(), 0)
+
+ # ordinary scenario
+ flat = Settings.flatten_tree({
+ 'session': {
+ 'debug': True,
+ 'paths': {
+ 'library': '/path/to/lib',
+ 'config': {'path': '/path/to/conf' },
+ }
+ }}, schema, on_error='raise')
+ self.assertDictEqual(flat, {
+ ('session', 'debug'): True, # defaults are kept
+ ('session', 'paths', 'library'): '/path/to/lib',
+ ('session', 'paths', 'config', 'path'): '/path/to/conf',
+ })
+
+ # dict types
+ flat = Settings.flatten_tree({
+ 'ui': {
+ 'standalone': {
+ 'tiledocks': {
+ 'dashboard': {
+ 'Buttons': 123
+ }}}}}, schema, on_error='raise')
+ self.assertDictEqual(flat, {
+ ('ui', 'standalone', 'tiledocks'): {'dashboard': {'Buttons': 123}}})
+
+ flat = Settings.flatten_tree({
+ 'ui': {
+ 'standalone': {
+ 'tiledocks': {
+ 'dashboard': {}
+ }}}}, schema)
+ self.assertDictEqual(flat, {
+ ('ui', 'standalone', 'tiledocks'): {'dashboard': {}}
+ })
+
+ # dict types w/o schema
+ flat = Settings.flatten_tree({
+ 'ui': {
+ 'standalone': {
+ 'tiledocks': {
+ 'dashboard': {
+ 'Buttons': 123
+ }}}}}, ConfigSchema(), on_error='raise')
+ self.assertDictEqual(flat, {
+ ('ui', 'standalone', 'tiledocks', 'dashboard', 'Buttons'): 123})
+
+ # dict types w/o schema
+ flat = Settings.flatten_tree({
+ 'ui': {
+ 'standalone': {
+ 'tiledocks': {
+ 'dashboard': {
+ 'Buttons': {}
+ }}}}}, ConfigSchema(), on_error='raise')
+ self.assertDictEqual(flat, {
+ ('ui', 'standalone', 'tiledocks', 'dashboard', 'Buttons'): {}})
+
+ # error case: invalid value
+ config = {
+ 'session': {
+ 'debug': 123,
+ 'paths': {
+ 'library': '/path/to/lib',
+ 'config': {'path': '/path/to/conf' },
+ }
+ }}
+ self.assertRaises(TypeError, Settings.flatten_tree, config, schema, on_error='raise')
+ flat = Settings.flatten_tree(config, schema, on_error='ignore')
+ self.assertDictEqual(flat, {
+ # debug is omitted because it's invalid
+ ('session', 'paths', 'library'): '/path/to/lib',
+ ('session', 'paths', 'config', 'path'): '/path/to/conf',
+ })
+ # error case: invalid subkey
+ config = {
+ 'session': {
+ 'debug': {
+ 'verbose': True
+ },
+ 'paths': {
+ 'library': '/path/to/lib',
+ 'config': {'path': '/path/to/conf' },
+ }
+ }}
+ self.assertRaises(TypeError, Settings.flatten_tree, config, schema, on_error='raise')
+ flat = Settings.flatten_tree(config, schema, on_error='ignore')
+ self.assertDictEqual(flat, {
+ # debug is omitted because it's invalid
+ ('session', 'paths', 'library'): '/path/to/lib',
+ ('session', 'paths', 'config', 'path'): '/path/to/conf',
+ })
+ # error case: invalid superkey
+ config = {
+ 'session': 123,
+ 'storage': {
+ 'library': {
+ 'autosave': -4,
+ 'write_through': True
+ }}}
+ self.assertRaises(TypeError, Settings.flatten_tree, config, schema, on_error='raise')
+ flat = Settings.flatten_tree(config, schema, on_error='ignore')
+ self.assertDictEqual(flat, {('storage', 'library', 'write_through'): True})
+
+ def test_clone(self):
+ cfg = self.cfg.clone()
+ self.assertEqual(self.cfg, cfg)
+ cfg.set(('session', 'paths', 'library'), '/path/to/elsewhere')
+ self.assertEqual('/path/to/elsewhere', cfg('session', 'paths', 'library'))
+ self.assertEqual('/path/to/lib', self.cfg('session', 'paths', 'library'))
+ self.assertNotEqual(self.cfg, cfg)
+
+ def test_file_connected(self):
+ self.assertFalse(self.cfg.file_connected())
+ self.cfg.set(('session', 'paths', 'config'), '/path/to/somewhere')
+ self.assertTrue(self.cfg.file_connected())
+ self.cfg.unset('session', 'paths', 'config')
+ self.assertFalse(self.cfg.file_connected())
+
+ def test_schema_changes(self):
+ schema = ConfigSchema()
+ cfg = Settings.Open({
+ 'ui': {'standalone': {'tiledocks': {'Buttons': {'buttons': [1,2,3]},
+ 'Info': {},
+ }}},
+ 'session': {'paths': {'preview': {'files': 'thumbs'}},
+ 'debug': False,
+ 'size': 'will_become_invalid',
+ },
+ }, schema=schema)
+ self.assertDictEqual(cfg.config, {
+ ('ui', 'standalone', 'tiledocks', 'Buttons', 'buttons'): [1,2,3],
+ ('ui', 'standalone', 'tiledocks', 'Info'): {},
+ ('session', 'paths', 'preview', 'files'): 'thumbs',
+ ('session', 'debug'): False,
+ ('session', 'size'): 'will_become_invalid',
+ })
+
+ schema.declare(('ui', 'standalone', 'tiledocks'),
+ types.Dict(types.String(), types.Dict(types.String(), types.Any())), {})
+ schema.declare(('session', 'paths', 'preview'),
+ types.Dict(types.String(), types.String()), {})
+ schema.declare(('session', 'debug'), types.Bool(), False)
+
+ cfg.rebase(clear_defaults=False)
+ self.assertDictEqual(cfg.config, {
+ ('ui', 'standalone', 'tiledocks'): {'Buttons': {'buttons': [1,2,3]}, 'Info': {}},
+ ('session', 'paths', 'preview'): {'files': 'thumbs'},
+ ('session', 'debug'): False, # is still in here
+ ('session', 'size'): 'will_become_invalid',
+ })
+
+ cfg.rebase()
+ self.assertDictEqual(cfg.config, {
+ ('ui', 'standalone', 'tiledocks'): {'Buttons': {'buttons': [1,2,3]}, 'Info': {}},
+ ('session', 'paths', 'preview'): {'files': 'thumbs'},
+ # now session.debug is gone
+ ('session', 'size'): 'will_become_invalid',
+ })
+
+ # create a schema violation
+ schema.declare(('session', 'size'), types.Int(), 0)
+ # raises an error
+ self.assertRaises(TypeError, cfg.rebase)
+ # ignore the error, size will be removed
+ cfg.rebase(on_error='ignore')
+ self.assertDictEqual(cfg.config, {
+ ('ui', 'standalone', 'tiledocks'): {'Buttons': {'buttons': [1,2,3]}, 'Info': {}},
+ ('session', 'paths', 'preview'): {'files': 'thumbs'},
+ })
+
+ def test_to_tree(self):
+ self.assertDictEqual(self.cfg.to_tree(), {
+ 'session': {'paths': {'library': '/path/to/lib'},
+ 'messaging': {'verbose': 8}},
+ })
+ self.assertDictEqual(self.cfg.to_tree(defaults=True), {
+ 'session': {'paths': {'library': '/path/to/lib',
+ 'config': ''},
+ 'messaging': {'verbose': 8}},
+ 'ui': {'standalone': {'browser': {'cols': 3}}},
+ 'extraction': {'constant': {'enabled': False}}
+ })
+ # corner cases
+ self.assertDictEqual(Settings(schema=self.schema, data={}).to_tree(), {})
+ self.assertDictEqual(Settings(schema=self.schema, data={('session', ): True}).to_tree(),
+ {'session': True})
+ # tree from subkey
+ self.assertDictEqual(self.cfg('session', 'paths').to_tree(), {
+ 'library': '/path/to/lib'})
+ self.assertDictEqual(self.cfg('session', 'paths').to_tree(defaults=True), {
+ 'library': '/path/to/lib',
+ 'config': ''})
+
+ def test_Open_formats(self):
+ config = {
+ 'session': {
+ 'paths': {
+ 'library': '/path/to/somewhere',
+ 'numerical': '/path/to/numerical',
+ },
+ 'messaging': {
+ 'debug': True,
+ 'verbose': False,
+ },
+ },
+ 'extraction': {
+ 'constant': {
+ 'enabled': False
+ },
+ }}
+
+ # store to file
+ path = tempfile.mkstemp(prefix='tagit_')[1]
+ with open(path, 'w') as ofile:
+ json.dump(config, ofile)
+
+ # load from json string
+ cfg = Settings.Open(json.dumps(config), schema=self.schema)
+ # default
+ self.assertEqual(3, cfg('ui', 'standalone', 'browser', 'cols'))
+ self.assertEqual(False, cfg('extraction', 'constant', 'enabled'))
+ # invalid
+ self.assertNotIn(('ui', 'standalone', 'browser', 'rows'), cfg)
+ # known
+ self.assertEqual('/path/to/somewhere', cfg('session', 'paths', 'library'))
+ # unknown
+ self.assertEqual('/path/to/numerical', cfg('session', 'paths', 'numerical'))
+ self.assertTrue(cfg('session', 'messaging', 'debug'))
+ self.assertFalse(cfg('session', 'messaging', 'verbose'))
+ # config dict
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'library'): '/path/to/somewhere',
+ ('session', 'paths', 'numerical'): '/path/to/numerical',
+ ('session', 'messaging', 'debug'): True,
+ ('session', 'messaging', 'verbose'): False,
+ })
+
+ # load from dict
+ cfg2 = Settings.Open(config, schema=self.schema)
+ self.assertDictEqual(cfg.config, cfg2.config)
+
+ # load from file
+ cfg2 = Settings.Open(path, schema=self.schema)
+ self.assertDictEqual(cfg.config, cfg2.config)
+
+ # load from opened file
+ with open(path) as ifile:
+ cfg2 = Settings.Open(ifile, schema=self.schema)
+ self.assertDictEqual(cfg.config, cfg2.config)
+
+ # invalid type
+ self.assertRaises(TypeError, Settings.Open, 15, schema=self.schema)
+
+ os.unlink(path)
+
+ def test_Open_errors(self):
+ # invalid value
+ config = {'session': {'paths': {'library': 15}}}
+ self.assertRaises(types.ConfigTypeError, Settings.Open, config, schema=self.schema)
+ # too deep
+ config = {'session': {'paths': {'library': {'plain': '/path/to/somewhere'}}}}
+ self.assertRaises(types.ConfigTypeError, Settings.Open, config, schema=self.schema)
+ # too shallow
+ config = {'session': {'paths': 123}}
+ self.assertRaises(ConfigError, Settings.Open, config, schema=self.schema)
+ # empty
+ cfg = Settings.Open({}, schema=self.schema)
+ self.assertDictEqual(cfg.config, {})
+
+ def test_Open_dict_keys(self):
+ schema = ConfigSchema()
+ schema.declare(('ui', 'standalone', 'tiledocks'),
+ types.Dict(types.String(), types.Dict(types.String(), types.Any())), {})
+ # correct settings
+ config = {
+ 'ui': {
+ 'standalone': {
+ 'tiledocks': {
+ 'dashboard': {
+ 'Buttons': {
+ 'buttons': ['ShowBrowsing', 'ShowDashboard']
+ },
+ 'Info': {},
+ },
+ 'sidebar': {
+ 'LibSummary': {'size': 200},
+ },
+ }}}}
+ cfg = Settings.Open(config, schema=schema)
+ self.assertDictEqual(cfg('ui', 'standalone', 'tiledocks'),
+ config['ui']['standalone']['tiledocks'])
+ self.assertNotIn(('ui', 'standalone', 'tiledocks', 'dashboard'), cfg)
+
+ # incorrect settings
+ config = {
+ 'ui': {
+ 'standalone': {
+ 'tiledocks': {
+ 'dashboard': {
+ 'Buttons': {
+ 'buttons': ['ShowBrowsing', 'ShowDashboard']
+ },
+ 'Info': {},
+ },
+ 'sidebar': [ 'LibSummary' ],
+ }}}}
+ self.assertRaises(types.ConfigTypeError, Settings.Open, config, schema=schema)
+
+ def test_Open_sequence(self):
+ # start with empty config
+ schema = ConfigSchema()
+ schema.declare(('session', 'paths', 'library'), types.Path(), '')
+ schema.declare(('session', 'verbose'), types.Int(), 0)
+ schema.declare(('session', 'debug'), types.Bool(), False)
+
+ cfg = Settings(schema=schema)
+ # update from first source
+ cfg.update(Settings.Open({
+ 'session': {'verbose': 1}
+ }, schema=schema, clear_defaults=False))
+ # update from second source
+ cfg.update(Settings.Open({
+ 'session': {'paths': {'library': 'foobar'},
+ 'debug': True}
+ }, schema=schema, clear_defaults=False))
+ # update from third source
+ cfg.update(Settings.Open({
+ 'session': {'paths': {'library': 'barfoo'},
+ 'debug': False}
+ }, schema=schema, clear_defaults=False))
+ # check: the default value was cleared
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'library'): 'barfoo',
+ ('session', 'verbose'): 1,
+ })
+ # update from fourth source
+ cfg.update(Settings.Open({
+ 'session': {'paths': {'library': '/path/to/lib'},
+ 'verbose': 0}
+ }, schema=schema, clear_defaults=False))
+ # check: the default value is again cleared
+ self.assertDictEqual(cfg.config, {
+ ('session', 'paths', 'library'): '/path/to/lib',
+ })
+
+ def test_update(self):
+ # plain test
+ self.cfg.update(Settings(schema=self.schema, data={
+ ('session', 'paths', 'library'): '/path/to/elsewhere',
+ ('session', 'paths', 'config'): '/path/to/somewhere',
+ ('ui', 'standalone', 'browser', 'cols'): 3,
+ ('ui', 'standalone', 'browser', 'rows'): 5,
+ }))
+ self.assertDictEqual(self.cfg.config, {
+ ('session', 'paths', 'library'): '/path/to/elsewhere', # overwrite knowns
+ ('session', 'paths', 'config'): '/path/to/somewhere', # add new knowns
+ # don't add defaults
+ ('ui', 'standalone', 'browser', 'rows'): 5, # add new unknowns
+ ('session', 'messaging', 'verbose'): 8, # keep old values
+ })
+ # reset to default
+ self.cfg.update(Settings(schema=self.schema, data={
+ ('session', 'paths', 'library'): '',
+ ('session', 'messaging', 'verbose'): 5
+ }))
+ self.assertDictEqual(self.cfg.config, {
+ # removed due to becoming default
+ ('session', 'paths', 'config'): '/path/to/somewhere',
+ ('ui', 'standalone', 'browser', 'rows'): 5,
+ ('session', 'messaging', 'verbose'): 5, # overwrite unknowns
+ })
+
+ # error: invalid value
+ self.assertRaises(TypeError, self.cfg.update,
+ Settings(schema=self.schema, data={
+ ('session', 'paths', 'library'): 15,
+ ('ui', 'standalone', 'browser', 'rows'): 5,
+ }))
+
+ # error: known superkey
+ self.assertRaises(TypeError, self.cfg.update,
+ Settings(schema=self.schema, data={
+ ('session', 'paths'): 15,
+ }))
+ # error: known subkey
+ self.assertRaises(TypeError, self.cfg.update,
+ Settings(schema=self.schema, data={
+ ('session', 'paths', 'config', 'write_through'): False,
+ }))
+
+ # error: unknown superkey
+ self.assertRaises(TypeError, self.cfg.update,
+ Settings(schema=self.schema, data={
+ ('session', 'messaging'): 15,
+ ('ui', 'standalone', 'browser', 'rows'): 5,
+ }))
+ # error: unknown subkey
+ self.assertRaises(TypeError, self.cfg.update,
+ Settings(schema=self.schema, data={
+ ('session', 'messaging', 'verbose', 'debug'): True,
+ ('ui', 'standalone', 'browser', 'rows'): 5,
+ }))
+
+ # error: unconfigured known superkey
+ self.assertRaises(TypeError, Settings(schema=self.schema, data={
+ }).update, Settings(schema=ConfigSchema(), data={
+ ('ui', 'standalone', 'browser'): 15
+ }))
+ # error: unconfigured known subkey
+ self.assertRaises(TypeError, Settings(schema=self.schema, data={
+ }).update, Settings(schema=ConfigSchema(), data={
+ ('ui', 'standalone', 'browser', 'cols', 'foo'): 15
+ }))
+
+ # corner case: overwrite unknown subkey with dict
+ cfg = Settings(schema=ConfigSchema(), data={
+ ('view', 'toolbag', 'right', 'Info'): 3
+ }).update(Settings(schema=ConfigSchema(), data={
+ ('view', 'toolbag', 'right'): {}
+ }))
+ self.assertDictEqual(cfg.config, {
+ ('view', 'toolbag', 'right'): {}
+ })
+
+ def test_save(self):
+ # needs uri
+ self.assertRaises(ValueError, self.cfg.save)
+ # can save if uri is known
+ path = tempfile.mkstemp(prefix='tagit_')[1]
+ self.assertEqual(os.stat(path).st_size, 0)
+ self.cfg.save(path)
+ # file has changed
+ self.assertGreater(os.stat(path).st_size, 0)
+ # can restore from file
+ cfg = Settings.Open(path, schema=self.schema)
+ self.assertEqual(cfg, self.cfg)
+ self.assertDictEqual(cfg.config, self.cfg.config)
+ # can save to buffer
+ buf = io.StringIO()
+ self.cfg.save(buf)
+ with open(path) as ifile:
+ self.assertEqual(buf.getvalue(), ifile.read())
+ os.unlink(path)
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/config/test_types.py b/test/config/test_types.py
new file mode 100644
index 0000000..31537e6
--- /dev/null
+++ b/test/config/test_types.py
@@ -0,0 +1,251 @@
+"""
+
+Part of the tagit test suite.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# imports
+import unittest
+
+# tagit imports
+from tagit.config.types import ConfigTypeError
+
+# objects to test
+from tagit.config import types
+
+
+## code ##
+
+class TestTypes(unittest.TestCase):
+ def test_any(self):
+ inst = types.Any()
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'Any')
+ self.assertEqual(repr(inst), 'Any()')
+ # comparison (eq, hash)
+ self.assertEqual(types.Any(), types.Any())
+ self.assertEqual(hash(types.Any()), hash(types.Any()))
+ # backtrack
+ self.assertIsNone(inst.backtrack(None, ''))
+ self.assertIsNone(inst.backtrack('foobar', ''))
+ self.assertIsNone(inst.backtrack(123, ''))
+ self.assertIsNone(inst.backtrack([], ''))
+ self.assertIsNone(inst.backtrack(inst, ''))
+
+ def test_bool(self):
+ inst = types.Bool()
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'Bool')
+ self.assertEqual(repr(inst), 'Bool()')
+ # comparison (eq, hash)
+ self.assertEqual(types.Bool(), types.Bool())
+ self.assertEqual(hash(types.Bool()), hash(types.Bool()))
+ # backtrack
+ self.assertIsNone(inst.backtrack(True, ''))
+ self.assertIsNone(inst.backtrack(False, ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 123, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, inst, '')
+
+ def test_keybind(self):
+ inst = types.Keybind()
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'Keybind')
+ self.assertEqual(repr(inst), 'Keybind()')
+ # comparison (eq, hash)
+ self.assertEqual(types.Keybind(), types.Keybind())
+ self.assertEqual(hash(types.Keybind()), hash(types.Keybind()))
+ # backtrack
+ self.assertIsNone(inst.backtrack([], ''))
+ self.assertIsNone(inst.backtrack([(5, ('ctrl', ), ('all', ))], ''))
+ self.assertIsNone(inst.backtrack([('abc', ('rest', ), ('all', ))], ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 123, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, inst, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [1,2,3], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [(1,2,3)], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [(1,(3,),3)], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [(1,3,(3,))], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [(3,(3,),('all',))], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [(3,('foobar',),('all',))], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [(3,('all',),(3,))], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [(3,('all',),('foobar',))], '')
+
+ def test_int(self):
+ inst = types.Int()
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'Int')
+ self.assertEqual(repr(inst), 'Int()')
+ # comparison (eq, hash)
+ self.assertEqual(types.Int(), types.Int())
+ self.assertEqual(hash(types.Int()), hash(types.Int()))
+ # backtrack
+ self.assertIsNone(inst.backtrack(0, ''))
+ self.assertIsNone(inst.backtrack(1, ''))
+ self.assertIsNone(inst.backtrack(-1, ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, inst, '')
+
+ def test_unsigned(self):
+ inst = types.Unsigned()
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'Unsigned int')
+ self.assertEqual(repr(inst), 'Unsigned()')
+ # comparison (eq, hash)
+ self.assertEqual(types.Unsigned(), types.Unsigned())
+ self.assertEqual(hash(types.Unsigned()), hash(types.Unsigned()))
+ # backtrack
+ self.assertIsNone(inst.backtrack(0, ''))
+ self.assertIsNone(inst.backtrack(1, ''))
+ self.assertIsNone(inst.backtrack(123, ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, -1, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, inst, '')
+
+ def test_float(self):
+ inst = types.Float()
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'Float')
+ self.assertEqual(repr(inst), 'Float()')
+ # comparison (eq, hash)
+ self.assertEqual(types.Float(), types.Float())
+ self.assertEqual(hash(types.Float()), hash(types.Float()))
+ # backtrack
+ self.assertIsNone(inst.backtrack(1.23, ''))
+ self.assertIsNone(inst.backtrack(-1.23, ''))
+ self.assertIsNone(inst.backtrack(1, ''))
+ self.assertIsNone(inst.backtrack(-1, ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, inst, '')
+
+ def test_string(self):
+ inst = types.String()
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'String')
+ self.assertEqual(repr(inst), 'String()')
+ # comparison (eq, hash)
+ self.assertEqual(types.String(), types.String())
+ self.assertEqual(hash(types.String()), hash(types.String()))
+ # backtrack
+ self.assertIsNone(inst.backtrack('', ''))
+ self.assertIsNone(inst.backtrack('foobar', ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 123, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, inst, '')
+
+ def test_path(self):
+ inst = types.Path()
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'Path')
+ self.assertEqual(repr(inst), 'Path()')
+ # comparison (eq, hash)
+ self.assertEqual(types.Path(), types.Path())
+ self.assertEqual(hash(types.Path()), hash(types.Path()))
+ # backtrack
+ self.assertIsNone(inst.backtrack('', ''))
+ self.assertIsNone(inst.backtrack('foobar', ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 123, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, inst, '')
+
+ def test_enum(self):
+ inst = types.Enum('foo', 'bar', 123)
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'One out of ({})'.format(
+ ', '.join(str(itm) for itm in inst.options)))
+ self.assertEqual(repr(inst), f'Enum([{inst.options}])')
+ # comparison (eq, hash)
+ self.assertEqual(types.Enum(), types.Enum())
+ self.assertEqual(types.Enum('foo', 'bar'), types.Enum(['foo', 'bar']))
+ self.assertEqual(types.Enum(123, 'bar'), types.Enum(['bar', 123]))
+ self.assertEqual(hash(types.Enum('foo')), hash(types.Enum(['foo'])))
+ # backtrack
+ self.assertIsNone(inst.backtrack('foo', ''))
+ self.assertIsNone(inst.backtrack('bar', ''))
+ self.assertIsNone(inst.backtrack(123, ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 'foobar', '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, inst, '')
+
+ def test_list(self):
+ inst = types.List(types.Int())
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'List of Int')
+ self.assertEqual(repr(inst), 'List(Int)')
+ # comparison (eq, hash)
+ self.assertEqual(types.List(types.Int()), types.List(types.Int()))
+ self.assertNotEqual(types.List(types.Int()), types.List(types.Unsigned()))
+ self.assertNotEqual(types.List(types.Unsigned()), types.List(types.Int()))
+ self.assertNotEqual(types.List(types.Unsigned()), types.List(types.String()))
+ self.assertNotEqual(types.List(types.Bool()), types.List(types.String()))
+ self.assertEqual(hash(types.List(types.Int())), hash(types.List(types.Int())))
+ self.assertEqual(hash(types.List(types.Unsigned())), hash(types.List(types.Unsigned())))
+ self.assertEqual(hash(types.List(types.String())), hash(types.List(types.String())))
+ # backtrack
+ self.assertIsNone(inst.backtrack([], ''))
+ self.assertIsNone(inst.backtrack([1,2,3], ''))
+ self.assertIsNone(inst.backtrack((1,2,3), ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, {1,2,3}, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 'foobar', '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, ['a', 'b', 'c'], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [123, 'b', 'c'], '')
+
+ def test_dict(self):
+ inst = types.Dict(types.String(), types.Int())
+ # representation (str, repr)
+ self.assertEqual(str(inst), 'Dict from String to Int')
+ self.assertEqual(repr(inst), 'Dict(String, Int)')
+ # comparison (eq, hash)
+ self.assertEqual(types.Dict(types.Int(), types.String()),
+ types.Dict(types.Int(), types.String()))
+ self.assertEqual(types.Dict(types.List(types.Int()), types.String()),
+ types.Dict(types.List(types.Int()), types.String()))
+ self.assertNotEqual(types.Dict(types.Int(), types.Unsigned()),
+ types.Dict(types.Unsigned(), types.Int()))
+ self.assertNotEqual(types.Dict(types.Unsigned(), types.String()),
+ types.Dict(types.Int(), types.String()))
+ self.assertNotEqual(types.Dict(types.Unsigned(), types.String()),
+ types.Dict(types.Int(), types.String()))
+ self.assertEqual(hash(types.Dict(types.Int(), types.String())),
+ hash(types.Dict(types.Int(), types.String())))
+ self.assertEqual(hash(types.Dict(types.List(types.Int()), types.String())),
+ hash(types.Dict(types.List(types.Int()), types.String())))
+ # backtrack
+ self.assertIsNone(inst.backtrack({'foo': 2}, ''))
+ self.assertIsNone(inst.backtrack({'foo': 2, 'bar': 3}, ''))
+ self.assertIsNone(inst.backtrack({}, ''))
+ self.assertRaises(ConfigTypeError, inst.backtrack, None, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, {1,2,3}, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 'foobar', '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, ['a', 'b', 'c'], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, [123, 'b', 'c'], '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, {2: 'foo', 3: 'bar'}, '')
+ self.assertRaises(ConfigTypeError, inst.backtrack, {'foo': 2, 3: 'bar'}, '')
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##