def test_replace_attributes_only_overridden_if_None(self): for attrname in self.attributes[3:]: good = { attrname: '' } # a False value != None to make readonly work bad = {attrname: 'unwanted'} assert '' == getattr(Property().replace(**good), attrname) assert '' == getattr(Property(**good).replace(**bad), attrname)
def test_readonly_locks_value_and_description(self): p = Property('p') p.readonly = True with self.assertRaises(ConfigError): p.value = True with self.assertRaises(ConfigError): p.desc = "Lala"
def test_from_and_to_properties(self): properties = [Property('a'), Property('a.b', 5, int, '\d+', True, True, 'doc'), Property('b', 5, int, '\d+', True, True, 'doc')] conf = Configuration.from_properties(properties) assert properties == list(conf.to_properties()) assert 'a' in conf assert 'a.b' in conf assert 'b' in conf
def test_type_inferrence(self): for T in (bool, int, float, str, type(''),): assert T.__name__ == Property(value=T()).type, T class UnknownType: pass assert None == Property(value=UnknownType()).type assert 'float' == Property('', 4, float).type
def test_immutable(self): p = Property() assert p is not p.replace(**p.to_dict()) for attrname in self.attributes: try: setattr(p, attrname, None) except AttributeError: pass else: assert False, 'must not be able to change %r ' % (attrname,)
def test_immutable(self): p = Property() assert p is not p.replace(**p.to_dict()) for attrname in self.attributes: try: setattr(p, attrname, None) except AttributeError: pass else: assert False, 'must not be able to change %r ' % (attrname, )
def test_builder(self): properties = [Property('a', 5), Property('a.b', 6, int, '6.*', True, True, 'doc')] cb = cfg.ConfigBuilder() with cb['a'] as a: a.value = 5 with a['b'] as ab: ab.value = 6 ab.valid = '6.*' ab.readonly = True ab.hidden = True ab.doc = 'doc' assert properties == list(cb.to_configuration().to_properties())
def assert_value_conversion(kind, testvalue, expected): p = Property('test', testvalue, type=kind) actual = p.value self.assertEqual( expected, actual, ('Bad %s conversion for value: %r! expect: %r, actual: %r' % (kind, p.value, expected, actual)))
def test_all_public_attributes_names_are_reserved(self): public_attributes = set([n for n in dir(Property('test')) if not n.startswith('_')]) unreserved = public_attributes - set(Property._reserved()) self.assertTrue(len(unreserved) == 0, '''all public attributes of Configuraion objects must be reserved. unreserved: %s''' % (unreserved,))
def test_tupleness_attributes_and_defaults(self): """A property is a tuple with named values.""" default = OrderedDict.fromkeys(self.attributes, None) default['key'] = '' p = Property() assert tuple(default.values()) == p, p for attrname, value in default.items(): assert getattr(p, attrname) == value
def test_validation(self): p = Property('test') p.value = object() # uninitialized validation must allow all, including weird, values '''can't re-set validity string''' with self.assertRaises(AttributeError): p.validity = 'something' '''validation takes place in constructor''' with self.assertRaises(ValueError): Property('test', validity='brai+ns!') ### REGULAR EXPRESSIONS self.assertEqual('braiiins!', Property('test', 'braiiins!', validity='brai+ns!').value, 'validation is through regex') '''validation is by match, not by find''' with self.assertRaises(ValueError): Property('test', 'halfbrains!', validity='brai+ns!') '''validation is by full match''' with self.assertRaises(ValueError): Property('test', 'brains! supple brains!', validity='brai+ns!') ### NON-STRING VALUES self.assertEqual(99, Property('test', 99, validity='\d+').value, 'validation str(ingifies) value temporarily') ### WHITESPACE self.assertEqual('brains!', Property('test', '\f\n\r\t\vbrains! ', validity='brai+ns!').value, 'validation trims whitespace from value') self.assertEqual('brains!', Property('test', 'brains!', validity=' brai+ns!\f\n\r\t\v').value, 'validation trims whitespace from validity expression') '''validation respects explicit whitespace in regex''' with self.assertRaises(ValueError): Property('test', '\f\n\r\t\v XY ', validity=r'\f\n\r\t\v XY\s') # invalid because value gets trimmed self.assertEqual('brains!', Property('test', 'brains!', validity=' ^ brai+ns! $ ').value, 'leading ^ and trailing $ is ignored, even if embedded in whitespace') ### LISTS '''empty list must be ok''' p = Property('', type='list', validity='\d+') '''value must be validated as list''' p.value = ' 1, 123 ' with self.assertRaises(ValueError): p.value = '1, 123, None'
def test_replace_changes_existing(self): conf = Configuration.from_properties([Property('b', 'old')]) newvalues = {'b': 'replaced'} assert newvalues == conf.replace(newvalues)
def test_update(self): conf = Configuration.from_properties([Property('b', 'old')]) newvalues = {'b': 'replaced', 'c': 'new'} assert newvalues == conf.update(newvalues)
def test_validation_by_regex(self): assert 0 == Property('', 0, valid='[0-9]').value Property('', ['x'], valid='[0-9]')
def test_nonreserved_attributes_can_be_set(self): p = Property('test', 1) self.assertFalse('scattermonkey' in Property._reserved(), "precondition") p.scattermonkey = 9
def test_there_are_reserved_words(self): self.assertTrue(len(Property._reserved()) > 0)
def test_name_must_not_be_reserved(self): self.assertTrue('int' in Property._reserved(), "precondition") self.assertRaises(ConfigError, Property, name='int')
def test_replace_key(self): assert 'different.key' == Property().replace(key='different.key').key Property('some.key').replace(key='different.key')
def test_cannot_replace_if_readonly(self): Property(readonly=True).replace()
def test_replace_without_values(self): p = Property('a', 5, int, '\d+', False, False, 'doc') assert p == p.replace() assert p == p.replace(**dict.fromkeys(self.attributes))
def test_to_dict(self): p = Property('bla', 12, int, '\d+', True, True, '') assert p == Property(**p.to_dict())
def test_None_value_is_not_cast_or_validated(self): assert None == Property(type=bool, valid=lambda v: v is not None).value
def test_validation_by_callable(self): Property('', False, valid=lambda v: v)
def test_autocast(self): assert 13 == Property('', '13', int).value
def test_replace_type(self): assert 'int' == Property().replace(type=int).type assert 'int' == Property(type=int).replace(type=str).type
def test_key_is_normalized(self): assert 'x.y' == Property('X.Y').key
def test_typechecks_and_type_effects(self): self.assertEqual(-23.42, Property('test', "-23.42", type=float).value, 'builtin types can be used as type argument') ### CONSTRUCTOR self.assertEqual(11, Property('test', 11).value, 'without type, the value remains untransformed') self.assertEqual(11, Property('test', 11.5, type='int').value, 'with type, the value is transformed') self.assertEqual('', Property('test', None, type='str').value, 'with type, the value is transformed') self.assertRaises(configuration.TransformError, configuration.Transformers['int'], None) self.assertEqual(0, Property('test', None, type='int').value, 'with type, trying to set None sets Transformer default') self.assertFalse(Property('test', type='FRXMBL').type, 'unknown type will default to no type') ### ASSIGNMENT ## with type ## p = Property('test', type='int') '''can assign convertible values''' p.value = '0x10' self.assertEqual(16, p.value) '''can't assign inconvertible objects''' self.assertRaises(configuration.TransformError, configuration.Transformers['int'], object()) with self.assertRaises(TypeError): p.value = object() '''assigning None works though and goes to default''' p.value = None self.assertEqual(0, p.value) ## without type ## '''without value set, any crap can be assigned''' no_type = Property('no_type') crap = object() no_type.value = crap self.assertEqual(crap, no_type.value) '''with transformable-type value set, new value will be transformed or not accepted''' no_type = Property('no_type', 0) no_type.value = 3.14 self.assertEqual(3, no_type.value) with self.assertRaises(TypeError): no_type.value = 'not an int' '''with non-transformable-type value set, can assign value of same type''' no_type = Property('no_type', object()) no_type.value = 3.14 self.assertEqual(3.14, no_type.value) '''with non-transformable-type value set, can't assign different type''' no_type = Property('no_type', type(object)) with self.assertRaises(TypeError): no_type.value = 3.14
def test_replace_value(self): p = Property(value='original') assert 'new' == p.replace(value='new').value assert 'original' == p.replace(value=None).value
def test_reserved_attributes_cannot_be_set(self): p = Property('test', value=1) self.assertTrue('int' in Property._reserved(), "precondition") with self.assertRaises(AttributeError): p.int = 9
def test_attribute_access(self): p = Property('b', 5, int, '\d+', True, True, 'doc') conf = Configuration.from_properties([p]) assert 5 == conf['b'] assert p == conf.property('b')
def test_bad_value_for_type(self): Property('', 'a', int)