def test_modifier(self): a1 = MultiValueAttribute() a2 = MultiValueAttribute(immutable=True) Test = create_cls('Test', {'x': a1, 'y': a2}) t1 = Test() t1.x = (7, 33, 33) t1.x.add(5) self.assertEqual(t1.x, {5, 7, 33}) t1.x.add(7) self.assertEqual(t1.x, {5, 7, 33}) t1.x.discard(17) self.assertEqual(t1.x, {5, 7, 33}) t1.x.discard(7) self.assertEqual(t1.x, {5, 33}) t1.x.remove(5) self.assertEqual(t1.x, {33}) self.assertRaises(KeyError, t1.x.remove, 5) t1.x.clear() self.assertEqual(t1.x, set()) t1.x.update((1, 2, 2)) t1.x.update({7, 33}) self.assertEqual(t1.x, {1, 2, 7, 33}) i = t1.x.pop() self.assertEqual(t1.x, {1, 2, 7, 33} - {i}) t1.x.add(i) self.assertEqual(t1.x, {1, 2, 7, 33}) s = set(t1.x) o = {7, 18, 33, 90} t1.x |= o self.assertEqual(t1.x, s | o) t1.x = s t1.x.intersection_update(o) self.assertEqual(t1.x, s & o) t1.x = s t1.x &= o self.assertEqual(t1.x, s & o) t1.x = s t1.x.difference_update(o) self.assertEqual(t1.x, s - o) t1.x = s t1.x -= o self.assertEqual(t1.x, s - o) t1.x = s t1.x.symmetric_difference_update(o) self.assertEqual(t1.x, s.symmetric_difference(o)) # can't modify immutable attribute t1.y = {7} self.assertRaises(AttributeError, t1.y.add, 2) self.assertRaises(AttributeError, t1.y.discard, 8) self.assertRaises(AttributeError, t1.y.clear) self.assertRaises(AttributeError, t1.y.pop) self.assertRaises(AttributeError, t1.y.remove, 8) self.assertRaises(AttributeError, t1.y.update, (2, 9)) self.assertRaises(AttributeError, t1.y.__ior__, {2, 9}) self.assertRaises(AttributeError, t1.y.intersection_update, {2, 9}) self.assertRaises(AttributeError, t1.y.__iand__, {2, 9}) self.assertRaises(AttributeError, t1.y.difference_update, {2, 9}) self.assertRaises(AttributeError, t1.y.__isub__, {2, 9}) self.assertRaises(AttributeError, t1.y.symmetric_difference_update, {2, 9})
def test_converter(self): a = MultiValueAttribute(converter=str) Test = create_cls('Test', {'x': a}) t = Test() t.x = {5, 7, 'abc'} self.assertEqual(t.x, {'5', '7', 'abc'}) t.x = [int] self.assertEqual(t.x, {str(int)}) a = MultiValueAttribute(converter=int) Test = create_cls('Test', {'x': a}) t = Test() t.x = {5, '-9'} self.assertEqual(t.x, {-9, 5}) self.assertRaises(ValueError, setattr, t, 'x', ('17', '12.090')) self.assertRaises(TypeError, setattr, t, 'x', [object()])
def test_repr(self): x = MultiValueAttribute(default={1}) Test = create_cls('Test', {'x': x}) t1 = Test() self.assertRegex(repr(t1.x), '<.*\.%s: \%s>' % (Test.x.name, t1.x)) t1.x = {2, 7, 55} self.assertRegex(repr(t1.x), '<.*\.%s: \%s>' % (Test.x.name, t1.x))
class Car(Entity): id = UniqueIdAttribute() make = Attribute(immutable=True) model = Attribute(immutable=True) wheels = QualifiedMultiValueAttribute(WheelPosition, default={}) extras = MultiValueAttribute(default=set()) registered = Attribute(default=False) def __init__(self, make: str, model: str, *, listener: StateChangedListener = None): super().__init__() if listener: # create notifyer and add listener StateChangedNotifyerExtension(self).add_listener(listener) self.make = make self.model = model def change_tire(self, wheel_pos: WheelPosition, tire: Tire) -> None: try: wheel = self.wheels[wheel_pos] except KeyError: raise ValueError(f"No {wheel_pos} wheel") from None wheel.tire = tire # setting attribute of nested entity doesn't trigger change # notification, so it has to be done explicitely self.state_changed() def __repr__(self): """repr(self)""" return f"<{self.__class__.__name__}: {self.id} "
def test_constraints(self): # single constraint a = MultiValueAttribute(constraints=is_number) Test = create_cls('Test', {'x': a}) t = Test() t.x = {5} self.assertEqual(t.x, {5}) self.assertRaises(ValueError, setattr, t, 'x', {'a'}) # several constraints eq5 = lambda value: value == 5 a = MultiValueAttribute(constraints=(is_number, non_negative, between(1, 7), eq5)) Test = create_cls('Test', {'x': a}) t = Test() t.x = {5} self.assertEqual(t.x, {5}) self.assertRaises(ValueError, setattr, t, 'x', {'a'}) self.assertRaises(ValueError, setattr, t, 'x', {-3}) self.assertRaises(ValueError, setattr, t, 'x', {9}) self.assertRaises(ValueError, setattr, t, 'x', {2})
def test_access(self): a1 = MultiValueAttribute() a2 = MultiValueAttribute(default={1}) a3 = MultiValueAttribute(immutable=True) Test = create_cls('Test', {'x': a1, 'y': a2, 'z': a3}) t1 = Test() self.assertRaises(AttributeError, getattr, t1, 'x') t1.x = (7, 33, 33) self.assertEqual(t1.x, {7, 33}) del t1.x self.assertRaises(AttributeError, getattr, t1, 'x') self.assertIsNone(delattr(t1, 'x')) self.assertEqual(t1.y, {1}) t1.y = {5} self.assertEqual(t1.y, {5}) self.assertRaises(AttributeError, getattr, t1, 'z') t1.z = [7] self.assertEqual(t1.z, {7}) self.assertRaises(AttributeError, setattr, t1, 'z', {9}) self.assertRaises(AttributeError, delattr, t1, 'z') t2 = Test() self.assertRaises(AttributeError, getattr, t2, 'x') t2.x = (7, 33, 33) self.assertEqual(t2.x, {7, 33}) del t2.x self.assertRaises(AttributeError, getattr, t2, 'x') self.assertIsNone(delattr(t2, 'x')) self.assertEqual(t2.y, {1}) t2.y = {5} self.assertEqual(t2.y, {5}) self.assertRaises(AttributeError, getattr, t2, 'z') t2.z = [7] self.assertEqual(t2.z, {7}) self.assertRaises(AttributeError, setattr, t2, 'z', {9}) self.assertRaises(AttributeError, delattr, t2, 'z') # immutable object a1 = MultiValueAttribute() a2 = MultiValueAttribute(default={1}) a3 = MultiValueAttribute(immutable=True) Immu = immutable(create_cls('Immu', {'x': a1, 'y': a2, 'z': a3})) im1 = Immu() im1.x = ('a', ) self.assertEqual(im1.x, {'a'}) self.assertEqual(im1.y, {1}) im1.y = ('b', ) self.assertEqual(im1.y, {'b'}) self.assertRaises(AttributeError, getattr, im1, 'z') im1.z = {'a', 'b', 'c'} self.assertEqual(im1.z, {'a', 'b', 'c'}) for attr in ('x', 'y', 'z'): self.assertRaises(AttributeError, setattr, im1, attr, {3}) self.assertRaises(AttributeError, delattr, im1, attr)
def test_constructor(self): a = MultiValueAttribute() self.assertTrue(a.immutable is False) self.assertTrue(a.default is _NODEFAULT) self.assertTrue(a.converter is None) self.assertTrue(a.constraints is None) self.assertEqual(a._bound_constraints, ()) self.assertIsNone(a.__doc__) self.assertEqual(a.name, '<unnamed>') a = MultiValueAttribute(immutable=True, default={'5'}, converter=int, constraints=is_number, doc='doc') a.__set_name__(None, 'a') self.assertTrue(a.immutable is True) self.assertEqual(a.default, {5}) self.assertTrue(a.converter is int) self.assertTrue(a.constraints is is_number) self.assertTrue(type(a._bound_constraints) is tuple) self.assertTrue(all(callable(c) for c in a._bound_constraints)) self.assertEqual(a.__doc__, 'doc') self.assertEqual(a.name, 'a') # default must be iterable self.assertRaises(TypeError, MultiValueAttribute, default=3) # items in default must be compatible to converter and must pass # constraints self.assertRaises(TypeError, MultiValueAttribute, converter=int, default=[object()]) self.assertRaises(ValueError, MultiValueAttribute, converter=int, default=['1', 'a']) self.assertRaises(ValueError, MultiValueAttribute, constraints=(is_number, non_negative), default=[5, 9, 'a']) self.assertRaises(ValueError, MultiValueAttribute, constraints=(is_number, non_negative), default=[2, 3, 19, -7])
class PMVATest: """Helper class to test pickling instances with a MultiValueAttribute""" x = MultiValueAttribute(constraints=(is_number, non_negative))
def test_default(self): double_x = lambda t: {2 * i for i in t.x} a1 = MultiValueAttribute(default={17}) a2 = MultiValueAttribute(default=double_x) Test = create_cls('Test', {'x': a1, 'y': a2}) t = Test() self.assertEqual(t.x, {17}) self.assertEqual(t.y, {34}) t.x = {4, 70} self.assertEqual(t.x, {4, 70}) self.assertEqual(t.y, {8, 140}) del t.x # reset to default self.assertEqual(t.x, {17}) self.assertEqual(t.y, {34}) t.x.add(7) self.assertEqual(t.x, {7, 17}) self.assertEqual(t.y, {14, 34}) t.y = {3} self.assertEqual(t.y, {3}) del t.x # reset to default self.assertEqual(t.y, {3}) del t.y # reset to default self.assertEqual(t.x, {17}) self.assertEqual(t.y, {34}) t.x.discard(17) self.assertEqual(t.x, set()) del t.x # reset to default t.x.pop() self.assertEqual(t.x, set()) del t.x # reset to default t.x.clear() self.assertEqual(t.x, set()) del t.x # reset to default t.x.remove(17) self.assertEqual(t.x, set()) del t.x # reset to default t.x.update((2, 9)) self.assertEqual(t.x, {2, 9, 17}) del t.x # reset to default t.x |= {2, 9} self.assertEqual(t.x, {2, 9, 17}) del t.x # reset to default t.x.intersection_update({2, 9}) self.assertEqual(t.x, set()) del t.x # reset to default t.x &= {2, 9} self.assertEqual(t.x, set()) del t.x # reset to default t.x.difference_update({2, 9}) self.assertEqual(t.x, {17}) del t.x # reset to default t.x -= {17} self.assertEqual(t.x, set()) del t.x # reset to default t.x.symmetric_difference_update({2, 9, 17}) self.assertEqual(t.x, {2, 9}) self.assertEqual(t.y, {4, 18}) # callable default also linked to instance self.assertIs(t.y._instance, t.x._instance) # modifying default does fix the attribute value t.y.add(2) self.assertEqual(t.y, {2, 4, 18}) t.x = set() self.assertEqual(t.y, {2, 4, 18}) # deleting the attribute value restores the default del t.y self.assertEqual(t.y, set())