summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Maw <richard.maw@codethink.co.uk>2013-09-20 14:22:31 +0000
committerRichard Maw <richard.maw@codethink.co.uk>2013-09-25 12:50:21 +0000
commit8a003f588136a9f8eda7df876ed6c059dd6658f4 (patch)
tree4d960f970d31c317070691486e8ce767ab162b86
parent8bb01ef1b85794b8c865dc1fdb50c02acbbe3216 (diff)
downloaddefinitions-8a003f588136a9f8eda7df876ed6c059dd6658f4.tar.gz
morphlib: Add SystemMetadataDir class
This provides access to the /baserock directory as if it were a dict, abstracting away the details of how to get data out of it. The abstraction is useful since it is easier to use than accessing /baserock yourself, and allows the storage format to be changed more easily. Keys with / in may be supported in the future. since there have been discussions about allowing morphologies to be placed in subdirectories. Adding this support would require creating and removing directory components when values are set and deleted respectively. Iterating would require using os.walk instead of glob.iglob, since python doesn't support ** in globs.
-rw-r--r--morphlib/__init__.py1
-rw-r--r--morphlib/systemmetadatadir.py87
-rw-r--r--morphlib/systemmetadatadir_tests.py75
3 files changed, 163 insertions, 0 deletions
diff --git a/morphlib/__init__.py b/morphlib/__init__.py
index b1e3c7c3..7eb3f975 100644
--- a/morphlib/__init__.py
+++ b/morphlib/__init__.py
@@ -78,6 +78,7 @@ import sourcepool
import stagingarea
import stopwatch
import sysbranchdir
+import systemmetadatadir
import tempdir
import util
import workspace
diff --git a/morphlib/systemmetadatadir.py b/morphlib/systemmetadatadir.py
new file mode 100644
index 00000000..eac5b446
--- /dev/null
+++ b/morphlib/systemmetadatadir.py
@@ -0,0 +1,87 @@
+# Copyright (C) 2013 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# =*= License: GPL-2 =*=
+
+
+import collections
+import glob
+import json
+import os
+
+
+class SystemMetadataDir(collections.MutableMapping):
+
+ '''An abstraction over the /baserock metadata directory.
+
+ This allows methods of iterating over it, and accessing it like
+ a dict.
+
+ The /baserock metadata directory contains information about all of
+ the chunks in a built system. It exists to provide traceability from
+ the input sources to the output.
+
+ If you create the object with smd = SystemMetadataDir('/baserock')
+ data = smd['key'] will read /baserock/key.meta and return its JSON
+ encoded contents as native python objects.
+
+ smd['key'] = data will write data to /baserock/key.meta as JSON
+
+ The key may not have '\0' characters in it since the underlying
+ system calls don't support embedded NUL bytes.
+
+ The key may not have '/' characters in it since we do not support
+ morphologies with slashes in their names.
+
+ '''
+
+ def __init__(self, metadata_path):
+ collections.MutableMapping.__init__(self)
+ self._metadata_path = metadata_path
+
+ def _join_path(self, *args):
+ return os.path.join(self._metadata_path, *args)
+
+ def _raw_path_iter(self):
+ return glob.iglob(self._join_path('*.meta'))
+
+ @staticmethod
+ def _check_key(key):
+ if any(c in key for c in "\0/"):
+ raise KeyError(key)
+
+ def __getitem__(self, key):
+ self._check_key(key)
+ try:
+ with open(self._join_path('%s.meta' % key), 'r') as f:
+ return json.load(f)
+ except IOError:
+ raise KeyError(key)
+
+ def __setitem__(self, key, value):
+ self._check_key(key)
+ with open(self._join_path('%s.meta' % key), 'w') as f:
+ json.dump(value, f, indent=4, sort_keys=True)
+
+ def __delitem__(self, key):
+ self._check_key(key)
+ os.unlink(self._join_path('%s.meta' % key))
+
+ def __iter__(self):
+ return (os.path.basename(fn)[:-len('.meta')]
+ for fn in self._raw_path_iter())
+
+ def __len__(self):
+ return len(list(self._raw_path_iter()))
diff --git a/morphlib/systemmetadatadir_tests.py b/morphlib/systemmetadatadir_tests.py
new file mode 100644
index 00000000..0126f862
--- /dev/null
+++ b/morphlib/systemmetadatadir_tests.py
@@ -0,0 +1,75 @@
+# Copyright (C) 2013 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# =*= License: GPL-2 =*=
+
+
+import operator
+import os
+import shutil
+import tempfile
+import unittest
+
+import morphlib
+
+
+class SystemMetadataDirTests(unittest.TestCase):
+
+ def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
+ self.metadatadir = os.path.join(self.tempdir, 'baserock')
+ os.mkdir(self.metadatadir)
+ self.smd = morphlib.systemmetadatadir.SystemMetadataDir(
+ self.metadatadir)
+
+ def tearDown(self):
+ shutil.rmtree(self.tempdir)
+
+ def test_add_new(self):
+ self.smd['key'] = {'foo': 'bar'}
+ self.assertEqual(self.smd['key']['foo'], 'bar')
+
+ def test_replace(self):
+ self.smd['key'] = {'foo': 'bar'}
+ self.smd['key'] = {'foo': 'baz'}
+ self.assertEqual(self.smd['key']['foo'], 'baz')
+
+ def test_remove(self):
+ self.smd['key'] = {'foo': 'bar'}
+ del self.smd['key']
+ self.assertTrue('key' not in self.smd)
+
+ def test_iterate(self):
+ self.smd['build-essential'] = "Some data"
+ self.smd['core'] = "More data"
+ self.smd['foundation'] = "Yet more data"
+ self.assertEqual(sorted(self.smd.keys()),
+ ['build-essential', 'core', 'foundation'])
+ self.assertEqual(dict(self.smd.iteritems()),
+ {
+ 'build-essential': "Some data",
+ 'core': "More data",
+ 'foundation': "Yet more data",
+ })
+
+ def test_raises_KeyError(self):
+ self.assertRaises(KeyError, operator.getitem, self.smd, 'key')
+
+ def test_validates_keys(self):
+ for key in ('foo/bar', 'baz\0quux'):
+ self.assertRaises(KeyError, operator.getitem, self.smd, key)
+ self.assertRaises(KeyError, operator.setitem,
+ self.smd, key, 'value')
+ self.assertRaises(KeyError, operator.delitem, self.smd, key)