dulwich-users team mailing list archive
-
dulwich-users team
-
Mailing list archive
-
Message #00248
[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