← Back to team overview

dulwich-users team mailing list archive

[PATCH 1/3] Make TreeEntry a namedtuple for clarity.

 

From: Dave Borowitz <dborowitz@xxxxxxxxxx>

This provides a backwards-compatible API with negligible memory and
runtime overhead, and is especially easier to use when dealing with
multiple TreeEntry objects in parallel.

For benchmarks and discussion, see:
https://lists.launchpad.net/dulwich-users/msg00234.html

Change-Id: Ief7f66642e8b8b6a3550e008999af71e9b4b5dcc
---
 dulwich/_objects.c            |   27 +++++++++++++++++---
 dulwich/misc.py               |   54 +++++++++++++++++++++++++++++++++++++++++
 dulwich/objects.py            |    3 +-
 dulwich/tests/test_objects.py |   13 +++++++---
 4 files changed, 88 insertions(+), 9 deletions(-)

diff --git a/dulwich/_objects.c b/dulwich/_objects.c
index 47f4262..e4ab236 100644
--- a/dulwich/_objects.c
+++ b/dulwich/_objects.c
@@ -35,6 +35,8 @@ size_t strnlen(char *text, size_t maxlen)
 
 #define bytehex(x) (((x)<0xa)?('0'+(x)):('a'-0xa+(x)))
 
+static PyObject *tree_entry_cls;
+
 static PyObject *sha_to_pyhex(const unsigned char *sha)
 {
 	char hexsha[41];
@@ -147,7 +149,8 @@ int cmp_tree_item(const void *_a, const void *_b)
 static void free_tree_items(struct tree_item *items, int num) {
 	int i;
 	for (i = 0; i < num; i++) {
-		Py_DECREF(items[i].tuple);
+		if (items[i].tuple != NULL)
+			Py_DECREF(items[i].tuple);
 	}
 	free(items);
 }
@@ -205,8 +208,14 @@ static PyObject *py_sorted_tree_items(PyObject *self, PyObject *entries)
 		}
 		qsort_entries[i].name = PyString_AS_STRING(key);
 		qsort_entries[i].mode = PyInt_AS_LONG(py_mode);
-		qsort_entries[i].tuple = PyTuple_Pack(3, key, py_int_mode, py_sha);
+
+		qsort_entries[i].tuple = PyObject_CallFunctionObjArgs(
+				tree_entry_cls, key, py_int_mode, py_sha, NULL);
 		Py_DECREF(py_int_mode);
+		if (qsort_entries[i].tuple == NULL) {
+			free_tree_items(qsort_entries, i);
+			return NULL;
+		}
 		i++;
 	}
 
@@ -234,11 +243,21 @@ static PyMethodDef py_objects_methods[] = {
 	{ NULL, NULL, 0, NULL }
 };
 
-void init_objects(void)
+PyMODINIT_FUNC
+init_objects(void)
 {
-	PyObject *m;
+	PyObject *m, *misc_mod;
 
 	m = Py_InitModule3("_objects", py_objects_methods, NULL);
 	if (m == NULL)
 		return;
+
+	misc_mod = PyImport_ImportModule("dulwich.misc");
+	if (misc_mod == NULL)
+		return;
+
+	tree_entry_cls = PyObject_GetAttrString(misc_mod, "TreeEntry");
+	Py_DECREF(misc_mod);
+	if (tree_entry_cls == NULL)
+		return;
 }
diff --git a/dulwich/misc.py b/dulwich/misc.py
index 3a10b25..e9e0f01 100644
--- a/dulwich/misc.py
+++ b/dulwich/misc.py
@@ -99,3 +99,57 @@ def unpack_from(fmt, buf, offset=0):
     except AttributeError:
         b = buf[offset:offset+struct.calcsize(fmt)]
         return struct.unpack(fmt, b)
+
+
+try:
+    from collections import namedtuple
+
+    TreeEntry = namedtuple('TreeEntry', ['path', 'mode', 'sha'])
+except ImportError:
+    # Provide manual implementations of namedtuples for Python <2.5.
+    # If the class definitions change, be sure to keep these in sync by running
+    # namedtuple(..., verbose=True) in a recent Python and pasting the output.
+
+    # Necessary globals go here.
+    _tuple = tuple
+    _property = property
+    from operator import itemgetter as _itemgetter
+
+    class TreeEntry(tuple):
+            'TreeEntry(path, mode, sha)'
+
+            __slots__ = ()
+
+            _fields = ('path', 'mode', 'sha')
+
+            def __new__(_cls, path, mode, sha):
+                return _tuple.__new__(_cls, (path, mode, sha))
+
+            @classmethod
+            def _make(cls, iterable, new=tuple.__new__, len=len):
+                'Make a new TreeEntry object from a sequence or iterable'
+                result = new(cls, iterable)
+                if len(result) != 3:
+                    raise TypeError('Expected 3 arguments, got %d' % len(result))
+                return result
+
+            def __repr__(self):
+                return 'TreeEntry(path=%r, mode=%r, sha=%r)' % self
+
+            def _asdict(t):
+                'Return a new dict which maps field names to their values'
+                return {'path': t[0], 'mode': t[1], 'sha': t[2]}
+
+            def _replace(_self, **kwds):
+                'Return a new TreeEntry object replacing specified fields with new values'
+                result = _self._make(map(kwds.pop, ('path', 'mode', 'sha'), _self))
+                if kwds:
+                    raise ValueError('Got unexpected field names: %r' % kwds.keys())
+                return result
+
+            def __getnewargs__(self):
+                return tuple(self)
+
+            path = _property(_itemgetter(0))
+            mode = _property(_itemgetter(1))
+            sha = _property(_itemgetter(2))
diff --git a/dulwich/objects.py b/dulwich/objects.py
index ea69ec1..e406770 100644
--- a/dulwich/objects.py
+++ b/dulwich/objects.py
@@ -39,6 +39,7 @@ from dulwich.errors import (
 from dulwich.file import GitFile
 from dulwich.misc import (
     make_sha,
+    TreeEntry,
     )
 
 
@@ -733,7 +734,7 @@ def sorted_tree_items(entries):
         mode = int(mode)
         if not isinstance(hexsha, str):
             raise TypeError('Expected a string for SHA, got %r' % hexsha)
-        yield name, mode, hexsha
+        yield TreeEntry(name, mode, hexsha)
 
 
 def cmp_entry((name1, value1), (name2, value2)):
diff --git a/dulwich/tests/test_objects.py b/dulwich/tests/test_objects.py
index 195cac0..4fd4f42 100644
--- a/dulwich/tests/test_objects.py
+++ b/dulwich/tests/test_objects.py
@@ -30,6 +30,9 @@ import stat
 from dulwich.errors import (
     ObjectFormatException,
     )
+from dulwich.misc import (
+    TreeEntry,
+    )
 from dulwich.objects import (
     Blob,
     Tree,
@@ -425,9 +428,9 @@ _TREE_ITEMS = {
   }
 
 _SORTED_TREE_ITEMS = [
-  ('a.c', 0100755, 'd80c186a03f423a81b39df39dc87fd269736ca86'),
-  ('a', stat.S_IFDIR, 'd80c186a03f423a81b39df39dc87fd269736ca86'),
-  ('a/c', stat.S_IFDIR, 'd80c186a03f423a81b39df39dc87fd269736ca86'),
+  TreeEntry('a.c', 0100755, 'd80c186a03f423a81b39df39dc87fd269736ca86'),
+  TreeEntry('a', stat.S_IFDIR, 'd80c186a03f423a81b39df39dc87fd269736ca86'),
+  TreeEntry('a/c', stat.S_IFDIR, 'd80c186a03f423a81b39df39dc87fd269736ca86'),
   ]
 
 
@@ -477,7 +480,9 @@ class TreeTests(ShaFileCheckTests):
         def do_sort(entries):
             return list(sorted_tree_items(entries))
 
-        self.assertEqual(_SORTED_TREE_ITEMS, do_sort(_TREE_ITEMS))
+        actual = do_sort(_TREE_ITEMS)
+        self.assertEqual(_SORTED_TREE_ITEMS, actual)
+        self.assertTrue(isinstance(actual[0], TreeEntry))
 
         # C/Python implementations may differ in specific error types, but
         # should all error on invalid inputs.
-- 
1.7.2




References