[lxc-devel] [pylxd/master] Attribute state
rockstar on Github
lxc-bot at linuxcontainers.org
Tue Jun 28 18:37:24 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 663 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160628/0c6063d6/attachment.bin>
-------------- next part --------------
From 06d412fc9498c38268bb299b292af340a3fb57cd Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Mon, 27 Jun 2016 23:03:56 -0600
Subject: [PATCH 1/3] Remove __dirty__; opt for attributes keeping track of
state
---
pylxd/model.py | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/pylxd/model.py b/pylxd/model.py
index 2d08450..016a8e7 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -25,6 +25,8 @@ def __init__(self, validator=None, readonly=False):
self.validator = validator
self.readonly = False
+ self.dirty = False
+
class Manager(object):
"""A manager declaration.
@@ -95,14 +97,17 @@ class Model(object):
the instance is marked as dirty. `save` will save the changes
to the server.
"""
- __slots__ = ['client', '__dirty__']
+ __slots__ = ['client']
def __init__(self, client, **kwargs):
self.client = client
for key, val in kwargs.items():
setattr(self, key, val)
- self.__dirty__ = False
+ try:
+ self.__attributes__[key].dirty = False
+ except KeyError: # Manager or Parent attribute
+ pass
def __getattribute__(self, name):
try:
@@ -121,12 +126,15 @@ def __setattr__(self, name, value):
if attribute.validator is not None:
if attribute.validator is not type(value):
value = attribute.validator(value)
- self.__dirty__ = True
+ attribute.dirty = True
return super(Model, self).__setattr__(name, value)
@property
def dirty(self):
- return self.__dirty__
+ for name, attr in self.__attributes__.items():
+ if attr.dirty:
+ return True
+ return False
def sync(self):
"""Sync from the server.
From b7b97c0e87f1e69259c67fa162e72273ad69bd1a Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Tue, 28 Jun 2016 12:19:59 -0600
Subject: [PATCH 2/3] Keep a list of all dirty attributes
---
pylxd/model.py | 29 ++++++++++++++---------------
pylxd/tests/test_model.py | 31 ++++++++++++++++++++++++++++---
2 files changed, 42 insertions(+), 18 deletions(-)
diff --git a/pylxd/model.py b/pylxd/model.py
index 016a8e7..4508f0b 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -25,8 +25,6 @@ def __init__(self, validator=None, readonly=False):
self.validator = validator
self.readonly = False
- self.dirty = False
-
class Manager(object):
"""A manager declaration.
@@ -97,23 +95,21 @@ class Model(object):
the instance is marked as dirty. `save` will save the changes
to the server.
"""
- __slots__ = ['client']
+ __slots__ = ['client', '__dirty__']
def __init__(self, client, **kwargs):
+ self.__dirty__ = []
self.client = client
for key, val in kwargs.items():
setattr(self, key, val)
- try:
- self.__attributes__[key].dirty = False
- except KeyError: # Manager or Parent attribute
- pass
+ del self.__dirty__[:]
def __getattribute__(self, name):
try:
return super(Model, self).__getattribute__(name)
except AttributeError:
- if name in self.__slots__:
+ if name in self.__attributes__:
self.sync()
return super(Model, self).__getattribute__(name)
else:
@@ -126,17 +122,14 @@ def __setattr__(self, name, value):
if attribute.validator is not None:
if attribute.validator is not type(value):
value = attribute.validator(value)
- attribute.dirty = True
+ self.__dirty__.append(name)
return super(Model, self).__setattr__(name, value)
@property
def dirty(self):
- for name, attr in self.__attributes__.items():
- if attr.dirty:
- return True
- return False
+ return len(self.__dirty__) > 0
- def sync(self):
+ def sync(self, rollback=False):
"""Sync from the server.
When collections of objects are retrieved from the server, they
@@ -153,9 +146,14 @@ def sync(self):
raise exceptions.NotFound()
raise
for key, val in response.json()['metadata'].items():
- setattr(self, key, val)
+ if key not in self.__dirty__ or rollback:
+ setattr(self, key, val)
fetch = deprecated("fetch is deprecated; please use sync")(sync)
+ def rollback(self):
+ """Reset the object from the server."""
+ return self.sync(rollback=True)
+
def save(self, wait=False):
"""Save data to the server.
@@ -169,6 +167,7 @@ def save(self, wait=False):
if response.json()['type'] == 'async' and wait:
Operation.wait_for_operation(
self.client, response.json()['operation'])
+ del self.__dirty__[:]
update = deprecated('update is deprecated; please use save')(save)
def delete(self, wait=False):
diff --git a/pylxd/tests/test_model.py b/pylxd/tests/test_model.py
index bfcb4b9..efc61ff 100644
--- a/pylxd/tests/test_model.py
+++ b/pylxd/tests/test_model.py
@@ -21,14 +21,30 @@ class Item(model.Model):
age = model.Attribute(int)
data = model.Attribute()
- def sync(self):
- self.age = 1000
- self.data = {'key': 'val'}
+ @property
+ def api(self):
+ return self.client.api.items[self.name]
class TestModel(testing.PyLXDTestCase):
"""Tests for pylxd.model.Model."""
+ def setUp(self):
+ super(TestModel, self).setUp()
+
+ self.add_rule({
+ 'json': {
+ 'type': 'sync',
+ 'metadata': {
+ 'name': 'an-item',
+ 'age': 1000,
+ 'data': {'key': 'val'},
+ }
+ },
+ 'method': 'GET',
+ 'url': r'^http://pylxd.test/1.0/items/an-item',
+ })
+
def test_init(self):
"""Initial attributes are set."""
item = Item(self.client, name='an-item', age=15, data={'key': 'val'})
@@ -62,6 +78,15 @@ def test_unset_attribute_sync(self):
self.assertEqual(1000, item.age)
+ def test_rollback(self):
+ """Rollback resets the object from the server."""
+ item = Item(self.client, name='an-item', age=15, data={'key': 'val'})
+
+ item.age = 50
+ item.rollback()
+
+ self.assertEqual(1000, item.age)
+
def test_int_attribute_validator(self):
"""Age is set properly to be an int."""
item = Item(self.client)
From d18b62df1d8bf14b13d1e1afddf51383d419bc26 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at canonical.com>
Date: Tue, 28 Jun 2016 12:33:31 -0600
Subject: [PATCH 3/3] Add more tests for model (and fix a bug)
---
pylxd/model.py | 5 +++-
pylxd/tests/test_model.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 64 insertions(+), 2 deletions(-)
diff --git a/pylxd/model.py b/pylxd/model.py
index 4508f0b..818c1f8 100644
--- a/pylxd/model.py
+++ b/pylxd/model.py
@@ -23,7 +23,7 @@ class Attribute(object):
def __init__(self, validator=None, readonly=False):
self.validator = validator
- self.readonly = False
+ self.readonly = readonly
class Manager(object):
@@ -148,6 +148,8 @@ def sync(self, rollback=False):
for key, val in response.json()['metadata'].items():
if key not in self.__dirty__ or rollback:
setattr(self, key, val)
+ if rollback:
+ del self.__dirty__[:]
fetch = deprecated("fetch is deprecated; please use sync")(sync)
def rollback(self):
@@ -177,6 +179,7 @@ def delete(self, wait=False):
if response.json()['type'] == 'async' and wait:
Operation.wait_for_operation(
self.client, response.json()['operation'])
+ self.client = None
def marshall(self):
"""Marshall the object in preparation for updating to the server."""
diff --git a/pylxd/tests/test_model.py b/pylxd/tests/test_model.py
index efc61ff..a76b1fa 100644
--- a/pylxd/tests/test_model.py
+++ b/pylxd/tests/test_model.py
@@ -17,7 +17,7 @@
class Item(model.Model):
"""A fake model."""
- name = model.Attribute()
+ name = model.Attribute(readonly=True)
age = model.Attribute(int)
data = model.Attribute()
@@ -44,6 +44,22 @@ def setUp(self):
'method': 'GET',
'url': r'^http://pylxd.test/1.0/items/an-item',
})
+ self.add_rule({
+ 'json': {
+ 'type': 'sync',
+ 'metadata': {}
+ },
+ 'method': 'PUT',
+ 'url': r'^http://pylxd.test/1.0/items/an-item',
+ })
+ self.add_rule({
+ 'json': {
+ 'type': 'sync',
+ 'metadata': {}
+ },
+ 'method': 'DELETE',
+ 'url': r'^http://pylxd.test/1.0/items/an-item',
+ })
def test_init(self):
"""Initial attributes are set."""
@@ -78,6 +94,23 @@ def test_unset_attribute_sync(self):
self.assertEqual(1000, item.age)
+ def test_sync(self):
+ """A sync will update attributes from the server."""
+ item = Item(self.client, name='an-item')
+
+ item.sync()
+
+ self.assertEqual(1000, item.age)
+
+ def test_sync_dirty(self):
+ """Sync will not overwrite local attribute changes."""
+ item = Item(self.client, name='an-item')
+
+ item.age = 250
+ item.sync()
+
+ self.assertEqual(250, item.age)
+
def test_rollback(self):
"""Rollback resets the object from the server."""
item = Item(self.client, name='an-item', age=15, data={'key': 'val'})
@@ -86,6 +119,7 @@ def test_rollback(self):
item.rollback()
self.assertEqual(1000, item.age)
+ self.assertFalse(item.dirty)
def test_int_attribute_validator(self):
"""Age is set properly to be an int."""
@@ -116,3 +150,28 @@ def test_not_dirty(self):
item = Item(self.client, name='an-item', age=15, data={'key': 'val'})
self.assertFalse(item.dirty)
+
+ def test_marshall(self):
+ """The object is marshalled into a dict."""
+ item = Item(self.client, name='an-item', age=15, data={'key': 'val'})
+
+ result = item.marshall()
+
+ self.assertEqual({'age': 15, 'data': {'key': 'val'}}, result)
+
+ def test_delete(self):
+ """The object is deleted, and client is unset."""
+ item = Item(self.client, name='an-item', age=15, data={'key': 'val'})
+
+ item.delete()
+
+ self.assertIsNone(item.client)
+
+ def test_save(self):
+ """Attributes are written to the server; object is marked clean."""
+ item = Item(self.client, name='an-item', age=15, data={'key': 'val'})
+
+ item.age = 69
+ item.save()
+
+ self.assertFalse(item.dirty)
More information about the lxc-devel
mailing list