Skip to content

Commit 4c6ad44

Browse files
committed
Implement __repr__
1 parent 0535d92 commit 4c6ad44

File tree

2 files changed

+142
-1
lines changed

2 files changed

+142
-1
lines changed

immutables/_map.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2854,6 +2854,94 @@ map_py_dump(MapObject *self, PyObject *args)
28542854
}
28552855

28562856

2857+
static PyObject *
2858+
map_py_repr(MapObject *self)
2859+
{
2860+
Py_ssize_t i;
2861+
_PyUnicodeWriter writer;
2862+
2863+
2864+
i = Py_ReprEnter((PyObject *)self);
2865+
if (i != 0) {
2866+
return i > 0 ? PyUnicode_FromString("{...}") : NULL;
2867+
}
2868+
2869+
_PyUnicodeWriter_Init(&writer);
2870+
2871+
if (_PyUnicodeWriter_WriteASCIIString(
2872+
&writer, "<immutables.Map({", 17) < 0)
2873+
{
2874+
goto error;
2875+
}
2876+
2877+
MapIteratorState iter;
2878+
map_iter_t iter_res;
2879+
map_iterator_init(&iter, self->h_root);
2880+
int second = 0;
2881+
do {
2882+
PyObject *v_key;
2883+
PyObject *v_val;
2884+
2885+
iter_res = map_iterator_next(&iter, &v_key, &v_val);
2886+
if (iter_res == I_ITEM) {
2887+
if (second) {
2888+
if (_PyUnicodeWriter_WriteASCIIString(&writer, ", ", 2) < 0) {
2889+
goto error;
2890+
}
2891+
}
2892+
2893+
PyObject *s = PyObject_Repr(v_key);
2894+
if (s == NULL) {
2895+
goto error;
2896+
}
2897+
if (_PyUnicodeWriter_WriteStr(&writer, s) < 0) {
2898+
Py_DECREF(s);
2899+
goto error;
2900+
}
2901+
Py_DECREF(s);
2902+
2903+
if (_PyUnicodeWriter_WriteASCIIString(&writer, ": ", 2) < 0) {
2904+
goto error;
2905+
}
2906+
2907+
s = PyObject_Repr(v_val);
2908+
if (s == NULL) {
2909+
goto error;
2910+
}
2911+
if (_PyUnicodeWriter_WriteStr(&writer, s) < 0) {
2912+
Py_DECREF(s);
2913+
goto error;
2914+
}
2915+
Py_DECREF(s);
2916+
}
2917+
2918+
second = 1;
2919+
} while (iter_res != I_END);
2920+
2921+
if (_PyUnicodeWriter_WriteASCIIString(&writer, "})", 2) < 0) {
2922+
goto error;
2923+
}
2924+
2925+
PyObject *addr = PyUnicode_FromFormat(" at %p>", self);
2926+
if (addr == NULL) {
2927+
goto error;
2928+
}
2929+
if (_PyUnicodeWriter_WriteStr(&writer, addr) < 0) {
2930+
Py_DECREF(addr);
2931+
goto error;
2932+
}
2933+
Py_DECREF(addr);
2934+
2935+
Py_ReprLeave((PyObject *)self);
2936+
return _PyUnicodeWriter_Finish(&writer);
2937+
2938+
error:
2939+
_PyUnicodeWriter_Dealloc(&writer);
2940+
Py_ReprLeave((PyObject *)self);
2941+
return NULL;
2942+
}
2943+
2944+
28572945
static PyMethodDef Map_methods[] = {
28582946
{"set", (PyCFunction)map_py_set, METH_VARARGS, NULL},
28592947
{"get", (PyCFunction)map_py_get, METH_VARARGS, NULL},
@@ -2900,6 +2988,7 @@ PyTypeObject _Map_Type = {
29002988
.tp_new = map_tp_new,
29012989
.tp_weaklistoffset = offsetof(MapObject, h_weakreflist),
29022990
.tp_hash = PyObject_HashNotImplemented,
2991+
.tp_repr = (reprfunc)map_py_repr,
29032992
};
29042993

29052994

tests/test_map.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def __init__(self, hash, name, *, error_on_eq_to=None):
1616
self.error_on_eq_to = error_on_eq_to
1717

1818
def __repr__(self):
19+
if self._crasher is not None and self._crasher.error_on_repr:
20+
raise ReprError
1921
return '<Key name:{} hash:{}>'.format(self.name, self.hash)
2022

2123
def __hash__(self):
@@ -51,12 +53,19 @@ def __eq__(self, other):
5153
raise EqError
5254
return super().__eq__(other)
5355

56+
def __repr__(self, other):
57+
if HashKey._crasher is not None and HashKey._crasher.error_on_repr:
58+
raise ReprError
59+
return super().__eq__(other)
60+
5461

5562
class HaskKeyCrasher:
5663

57-
def __init__(self, *, error_on_hash=False, error_on_eq=False):
64+
def __init__(self, *, error_on_hash=False, error_on_eq=False,
65+
error_on_repr=False):
5866
self.error_on_hash = error_on_hash
5967
self.error_on_eq = error_on_eq
68+
self.error_on_repr = error_on_repr
6069

6170
def __enter__(self):
6271
if HashKey._crasher is not None:
@@ -75,6 +84,10 @@ class EqError(Exception):
7584
pass
7685

7786

87+
class ReprError(Exception):
88+
pass
89+
90+
7891
class MapTest(unittest.TestCase):
7992

8093
def test_hashkey_helper_1(self):
@@ -705,6 +718,45 @@ def test_map_getitem_1(self):
705718
with HaskKeyCrasher(error_on_hash=True):
706719
h[AA]
707720

721+
def test_repr_1(self):
722+
h = Map()
723+
self.assertTrue(repr(h).startswith('<immutables.Map({}) at 0x'))
724+
725+
h = h.set(1, 2).set(2, 3).set(3, 4)
726+
self.assertTrue(repr(h).startswith(
727+
'<immutables.Map({1: 2, 2: 3, 3: 4}) at 0x'))
728+
729+
def test_repr_2(self):
730+
h = Map()
731+
A = HashKey(100, 'A')
732+
733+
with self.assertRaises(ReprError):
734+
with HaskKeyCrasher(error_on_repr=True):
735+
repr(h.set(1, 2).set(A, 3).set(3, 4))
736+
737+
with self.assertRaises(ReprError):
738+
with HaskKeyCrasher(error_on_repr=True):
739+
repr(h.set(1, 2).set(2, A).set(3, 4))
740+
741+
def test_repr_3(self):
742+
class Key:
743+
def __init__(self):
744+
self.val = None
745+
746+
def __hash__(self):
747+
return 123
748+
749+
def __repr__(self):
750+
return repr(self.val)
751+
752+
h = Map()
753+
k = Key()
754+
h = h.set(k, 1)
755+
k.val = h
756+
757+
self.assertTrue(repr(h).startswith(
758+
'<immutables.Map({{...}: 1}) at 0x'))
759+
708760

709761
if __name__ == "__main__":
710762
unittest.main()

0 commit comments

Comments
 (0)