diff --git a/qubes/__init__.py b/qubes/__init__.py index 40f37594..eb0cb2f9 100644 --- a/qubes/__init__.py +++ b/qubes/__init__.py @@ -585,7 +585,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name _NO_DEFAULT = object() def __init__(self, name, setter=None, saver=None, type=None, - default=_NO_DEFAULT, load_stage=2, order=0, save_via_ref=False, + default=_NO_DEFAULT, write_once=False, load_stage=2, order=0, + save_via_ref=False, ls_head=None, ls_width=None, doc=None): # pylint: disable=redefined-builtin self.__name__ = name @@ -594,6 +595,7 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name lambda self, prop, value: str(value)) self._type = type self._default = default + self._write_once = write_once self.order = order self.load_stage = load_stage self.save_via_ref = save_via_ref @@ -628,6 +630,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name def __set__(self, instance, value): + self._enforce_write_once(instance) + if value is self.__class__.DEFAULT: self.__delete__(instance) return @@ -661,6 +665,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name def __delete__(self, instance): + self._enforce_write_once(instance) + try: oldvalue = getattr(instance, self.__name__) has_oldvalue = True @@ -700,6 +706,13 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name return self.__name__ == other.__name__ + def _enforce_write_once(self, instance): + if self._write_once and not instance.property_is_default(self): + raise AttributeError( + 'property {!r} is write-once and already set'.format( + self.__name__)) + + # # exceptions # @@ -806,17 +819,20 @@ class PropertyHolder(qubes.events.Emitter): ''' def __init__(self, xml, **kwargs): - super(PropertyHolder, self).__init__() self.xml = xml - for key, value in kwargs.items(): - setattr(self, key, value) + propvalues = {} all_names = set(prop.__name__ for prop in self.property_list()) for key in list(kwargs.keys()): if not key in all_names: continue - setattr(self, key, kwargs.pop(key)) + propvalues[key] = kwargs.pop(key) + + super(PropertyHolder, self).__init__(**kwargs) + + for key, value in propvalues.items(): + setattr(self, key, value) @classmethod diff --git a/qubes/tests/init.py b/qubes/tests/init.py index 15931215..2657e291 100644 --- a/qubes/tests/init.py +++ b/qubes/tests/init.py @@ -134,7 +134,7 @@ class TC_10_property(qubes.tests.QubesTestCase): self.assertEqual(holder.testprop1, 5) self.assertNotEqual(holder.testprop1, '5') - def test_090_delete(self): + def test_080_delete(self): self.holder.testprop1 = 'testvalue' try: if self.holder.testprop1 != 'testvalue': @@ -147,7 +147,7 @@ class TC_10_property(qubes.tests.QubesTestCase): with self.assertRaises(AttributeError): self.holder.testprop1 - def test_090_delete_by_assign(self): + def test_081_delete_by_assign(self): self.holder.testprop1 = 'testvalue' try: if self.holder.testprop1 != 'testvalue': @@ -160,7 +160,7 @@ class TC_10_property(qubes.tests.QubesTestCase): with self.assertRaises(AttributeError): self.holder.testprop1 - def test_092_delete_default(self): + def test_082_delete_default(self): class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): testprop1 = qubes.property('testprop1', default='defaultvalue') holder = MyTestHolder(None) @@ -176,6 +176,26 @@ class TC_10_property(qubes.tests.QubesTestCase): self.assertEqual(holder.testprop1, 'defaultvalue') + def test_090_write_once_set(self): + class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): + testprop1 = qubes.property('testprop1', write_once=True) + holder = MyTestHolder(None) + + holder.testprop1 = 'testvalue' + + with self.assertRaises(AttributeError): + holder.testprop1 = 'testvalue2' + + def test_091_write_once_delete(self): + class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): + testprop1 = qubes.property('testprop1', write_once=True) + holder = MyTestHolder(None) + + holder.testprop1 = 'testvalue' + + with self.assertRaises(AttributeError): + del holder.testprop1 + class TestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): testprop1 = qubes.property('testprop1', order=0) diff --git a/qubes/tests/tools/qvm_ls.py b/qubes/tests/tools/qvm_ls.py index d4f9c371..5063bf9a 100644 --- a/qubes/tests/tools/qvm_ls.py +++ b/qubes/tests/tools/qvm_ls.py @@ -59,15 +59,16 @@ class TC_00_Column(qubes.tests.QubesTestCase): class TC_90_globals(qubes.tests.QubesTestCase): # @qubes.tests.skipUnlessDom0 def test_100_simple_flag(self): - flag = qubes.tools.qvm_ls.simple_flag(1, 'T', 'qid') + flag = qubes.tools.qvm_ls.simple_flag(1, 'T', 'internal') # TODO after serious testing of QubesVM and Qubes app, this should be # using normal components app = qubes.tests.vm.adminvm.TestApp() - vm = qubes.vm.adminvm.AdminVM(app, None, qid=0, name='dom0') + vm = qubes.vm.adminvm.AdminVM(app, None, + qid=0, name='dom0', internal='False') self.assertFalse(flag(None, vm)) - vm.qid = 1 + vm.internal = 'True' self.assertTrue(flag(None, vm)) diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index f4bb2e0d..0fd68894 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -154,7 +154,7 @@ class QubesVM(qubes.vm.BaseVM): # type=bool, setter=qubes.property.bool, # doc='`True` if it is NetVM or ProxyVM, false otherwise.') - qid = qubes.property('qid', type=int, + qid = qubes.property('qid', type=int, write_once=True, setter=_setter_qid, ls_width=3, doc='''Internal, persistent identificator of particular domain. Note @@ -164,7 +164,7 @@ class QubesVM(qubes.vm.BaseVM): ls_width=31, doc='User-specified name of the domain.') - uuid = qubes.property('uuid', type=uuid.UUID, + uuid = qubes.property('uuid', type=uuid.UUID, write_once=True, ls_width=36, doc='UUID from libvirt.')