/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/index.py

1st cut merge of bzr.dev r3907

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007 Canonical Ltd
 
1
# Copyright (C) 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
27
27
from bisect import bisect_right
28
28
from cStringIO import StringIO
29
29
import re
 
30
import sys
30
31
 
31
32
from bzrlib.lazy_import import lazy_import
32
33
lazy_import(globals(), """
52
53
_newline_null_re = re.compile('[\n\0]')
53
54
 
54
55
 
 
56
def _has_key_from_parent_map(self, key):
 
57
    """Check if this index has one key.
 
58
 
 
59
    If it's possible to check for multiple keys at once through 
 
60
    calling get_parent_map that should be faster.
 
61
    """
 
62
    return (key in self.get_parent_map([key]))
 
63
 
 
64
 
 
65
def _missing_keys_from_parent_map(self, keys):
 
66
    return set(keys) - set(self.get_parent_map(keys))
 
67
 
 
68
 
55
69
class GraphIndexBuilder(object):
56
70
    """A builder that can build a GraphIndex.
57
71
    
80
94
        """
81
95
        self.reference_lists = reference_lists
82
96
        self._keys = set()
 
97
        # A dict of {key: (absent, ref_lists, value)}
83
98
        self._nodes = {}
84
 
        self._nodes_by_key = {}
 
99
        self._nodes_by_key = None
85
100
        self._key_length = key_elements
 
101
        self._optimize_for_size = False
86
102
 
87
103
    def _check_key(self, key):
88
104
        """Raise BadIndexKey if key is not a valid key for this index."""
94
110
            if not element or _whitespace_re.search(element) is not None:
95
111
                raise errors.BadIndexKey(element)
96
112
 
97
 
    def add_node(self, key, value, references=()):
98
 
        """Add a node to the index.
99
 
 
100
 
        :param key: The key. keys are non-empty tuples containing
101
 
            as many whitespace-free utf8 bytestrings as the key length
102
 
            defined for this index.
103
 
        :param references: An iterable of iterables of keys. Each is a
104
 
            reference to another key.
105
 
        :param value: The value to associate with the key. It may be any
106
 
            bytes as long as it does not contain \0 or \n.
 
113
    def _external_references(self):
 
114
        """Return references that are not present in this index.
 
115
        """
 
116
        keys = set()
 
117
        refs = set()
 
118
        # TODO: JAM 2008-11-21 This makes an assumption about how the reference
 
119
        #       lists are used. It is currently correct for pack-0.92 through
 
120
        #       1.9, which use the node references (3rd column) second
 
121
        #       reference list as the compression parent. Perhaps this should
 
122
        #       be moved into something higher up the stack, since it
 
123
        #       makes assumptions about how the index is used.
 
124
        if self.reference_lists > 1:
 
125
            for node in self.iter_all_entries():
 
126
                keys.add(node[1])
 
127
                refs.update(node[3][1])
 
128
            return refs - keys
 
129
        else:
 
130
            # If reference_lists == 0 there can be no external references, and
 
131
            # if reference_lists == 1, then there isn't a place to store the
 
132
            # compression parent
 
133
            return set()
 
134
 
 
135
    def _get_nodes_by_key(self):
 
136
        if self._nodes_by_key is None:
 
137
            nodes_by_key = {}
 
138
            if self.reference_lists:
 
139
                for key, (absent, references, value) in self._nodes.iteritems():
 
140
                    if absent:
 
141
                        continue
 
142
                    key_dict = nodes_by_key
 
143
                    for subkey in key[:-1]:
 
144
                        key_dict = key_dict.setdefault(subkey, {})
 
145
                    key_dict[key[-1]] = key, value, references
 
146
            else:
 
147
                for key, (absent, references, value) in self._nodes.iteritems():
 
148
                    if absent:
 
149
                        continue
 
150
                    key_dict = nodes_by_key
 
151
                    for subkey in key[:-1]:
 
152
                        key_dict = key_dict.setdefault(subkey, {})
 
153
                    key_dict[key[-1]] = key, value
 
154
            self._nodes_by_key = nodes_by_key
 
155
        return self._nodes_by_key
 
156
 
 
157
    def _update_nodes_by_key(self, key, value, node_refs):
 
158
        """Update the _nodes_by_key dict with a new key.
 
159
 
 
160
        For a key of (foo, bar, baz) create
 
161
        _nodes_by_key[foo][bar][baz] = key_value
 
162
        """
 
163
        if self._nodes_by_key is None:
 
164
            return
 
165
        key_dict = self._nodes_by_key
 
166
        if self.reference_lists:
 
167
            key_value = key, value, node_refs
 
168
        else:
 
169
            key_value = key, value
 
170
        for subkey in key[:-1]:
 
171
            key_dict = key_dict.setdefault(subkey, {})
 
172
        key_dict[key[-1]] = key_value
 
173
 
 
174
    def _check_key_ref_value(self, key, references, value):
 
175
        """Check that 'key' and 'references' are all valid.
 
176
 
 
177
        :param key: A key tuple. Must conform to the key interface (be a tuple,
 
178
            be of the right length, not have any whitespace or nulls in any key
 
179
            element.)
 
180
        :param references: An iterable of reference lists. Something like
 
181
            [[(ref, key)], [(ref, key), (other, key)]]
 
182
        :param value: The value associate with this key. Must not contain
 
183
            newlines or null characters.
 
184
        :return: (node_refs, absent_references)
 
185
            node_refs   basically a packed form of 'references' where all
 
186
                        iterables are tuples
 
187
            absent_references   reference keys that are not in self._nodes.
 
188
                                This may contain duplicates if the same key is
 
189
                                referenced in multiple lists.
107
190
        """
108
191
        self._check_key(key)
109
192
        if _newline_null_re.search(value) is not None:
111
194
        if len(references) != self.reference_lists:
112
195
            raise errors.BadIndexValue(references)
113
196
        node_refs = []
 
197
        absent_references = []
114
198
        for reference_list in references:
115
199
            for reference in reference_list:
116
 
                self._check_key(reference)
 
200
                # If reference *is* in self._nodes, then we know it has already
 
201
                # been checked.
117
202
                if reference not in self._nodes:
118
 
                    self._nodes[reference] = ('a', (), '')
 
203
                    self._check_key(reference)
 
204
                    absent_references.append(reference)
119
205
            node_refs.append(tuple(reference_list))
120
 
        if key in self._nodes and self._nodes[key][0] == '':
 
206
        return tuple(node_refs), absent_references
 
207
 
 
208
    def add_node(self, key, value, references=()):
 
209
        """Add a node to the index.
 
210
 
 
211
        :param key: The key. keys are non-empty tuples containing
 
212
            as many whitespace-free utf8 bytestrings as the key length
 
213
            defined for this index.
 
214
        :param references: An iterable of iterables of keys. Each is a
 
215
            reference to another key.
 
216
        :param value: The value to associate with the key. It may be any
 
217
            bytes as long as it does not contain \0 or \n.
 
218
        """
 
219
        (node_refs,
 
220
         absent_references) = self._check_key_ref_value(key, references, value)
 
221
        if key in self._nodes and self._nodes[key][0] != 'a':
121
222
            raise errors.BadIndexDuplicateKey(key, self)
122
 
        self._nodes[key] = ('', tuple(node_refs), value)
 
223
        for reference in absent_references:
 
224
            # There may be duplicates, but I don't think it is worth worrying
 
225
            # about
 
226
            self._nodes[reference] = ('a', (), '')
 
227
        self._nodes[key] = ('', node_refs, value)
123
228
        self._keys.add(key)
124
 
        if self._key_length > 1:
125
 
            key_dict = self._nodes_by_key
126
 
            if self.reference_lists:
127
 
                key_value = key, value, tuple(node_refs)
128
 
            else:
129
 
                key_value = key, value
130
 
            # possibly should do this on-demand, but it seems likely it is 
131
 
            # always wanted
132
 
            # For a key of (foo, bar, baz) create
133
 
            # _nodes_by_key[foo][bar][baz] = key_value
134
 
            for subkey in key[:-1]:
135
 
                key_dict = key_dict.setdefault(subkey, {})
136
 
            key_dict[key[-1]] = key_value
 
229
        if self._nodes_by_key is not None and self._key_length > 1:
 
230
            self._update_nodes_by_key(key, value, node_refs)
137
231
 
138
232
    def finish(self):
139
233
        lines = [_SIGNATURE]
142
236
        lines.append(_OPTION_LEN + str(len(self._keys)) + '\n')
143
237
        prefix_length = sum(len(x) for x in lines)
144
238
        # references are byte offsets. To avoid having to do nasty
145
 
        # polynomial work to resolve offsets (references to later in the 
 
239
        # polynomial work to resolve offsets (references to later in the
146
240
        # file cannot be determined until all the inbetween references have
147
241
        # been calculated too) we pad the offsets with 0's to make them be
148
242
        # of consistent length. Using binary offsets would break the trivial
221
315
                (len(result.getvalue()), expected_bytes))
222
316
        return result
223
317
 
 
318
    def set_optimize(self, for_size=True):
 
319
        """Change how the builder tries to optimize the result.
 
320
 
 
321
        :param for_size: Tell the builder to try and make the index as small as
 
322
            possible.
 
323
        :return: None
 
324
        """
 
325
        # GraphIndexBuilder itself doesn't pay attention to the flag yet, but
 
326
        # other builders do.
 
327
        self._optimize_for_size = for_size
 
328
 
224
329
 
225
330
class GraphIndex(object):
226
331
    """An index for data with embedded graphs.
272
377
        self._keys_by_offset = None
273
378
        self._nodes_by_key = None
274
379
        self._size = size
 
380
        # The number of bytes we've read so far in trying to process this file
 
381
        self._bytes_read = 0
275
382
 
276
383
    def __eq__(self, other):
277
384
        """Equal when self and other were created with the same parameters."""
288
395
        return "%s(%r)" % (self.__class__.__name__,
289
396
            self._transport.abspath(self._name))
290
397
 
291
 
    def _buffer_all(self):
 
398
    def _buffer_all(self, stream=None):
292
399
        """Buffer all the index data.
293
400
 
294
401
        Mutates self._nodes and self.keys_by_offset.
295
402
        """
 
403
        if self._nodes is not None:
 
404
            # We already did this
 
405
            return
296
406
        if 'index' in debug.debug_flags:
297
407
            mutter('Reading entire index %s', self._transport.abspath(self._name))
298
 
        stream = self._transport.get(self._name)
 
408
        if stream is None:
 
409
            stream = self._transport.get(self._name)
299
410
        self._read_prefix(stream)
300
411
        self._expected_elements = 3 + self._key_length
301
412
        line_count = 0
303
414
        self._keys_by_offset = {}
304
415
        # ready-to-return key:value or key:value, node_ref_lists
305
416
        self._nodes = {}
306
 
        self._nodes_by_key = {}
 
417
        self._nodes_by_key = None
307
418
        trailers = 0
308
419
        pos = stream.tell()
309
420
        lines = stream.read().split('\n')
318
429
            else:
319
430
                node_value = value
320
431
            self._nodes[key] = node_value
321
 
            if self._key_length > 1:
322
 
                subkey = list(reversed(key[:-1]))
323
 
                key_dict = self._nodes_by_key
324
 
                if self.node_ref_lists:
325
 
                    key_value = key, node_value[0], node_value[1]
326
 
                else:
327
 
                    key_value = key, node_value
328
 
                # possibly should do this on-demand, but it seems likely it is 
329
 
                # always wanted
330
 
                # For a key of (foo, bar, baz) create
331
 
                # _nodes_by_key[foo][bar][baz] = key_value
332
 
                for subkey in key[:-1]:
333
 
                    key_dict = key_dict.setdefault(subkey, {})
334
 
                key_dict[key[-1]] = key_value
335
432
        # cache the keys for quick set intersections
336
433
        self._keys = set(self._nodes)
337
434
        if trailers != 1:
338
435
            # there must be one line - the empty trailer line.
339
436
            raise errors.BadIndexData(self)
340
437
 
 
438
    def _get_nodes_by_key(self):
 
439
        if self._nodes_by_key is None:
 
440
            nodes_by_key = {}
 
441
            if self.node_ref_lists:
 
442
                for key, (value, references) in self._nodes.iteritems():
 
443
                    key_dict = nodes_by_key
 
444
                    for subkey in key[:-1]:
 
445
                        key_dict = key_dict.setdefault(subkey, {})
 
446
                    key_dict[key[-1]] = key, value, references
 
447
            else:
 
448
                for key, value in self._nodes.iteritems():
 
449
                    key_dict = nodes_by_key
 
450
                    for subkey in key[:-1]:
 
451
                        key_dict = key_dict.setdefault(subkey, {})
 
452
                    key_dict[key[-1]] = key, value
 
453
            self._nodes_by_key = nodes_by_key
 
454
        return self._nodes_by_key
 
455
 
341
456
    def iter_all_entries(self):
342
457
        """Iterate over all keys within the index.
343
458
 
468
583
            keys supplied. No additional keys will be returned, and every
469
584
            key supplied that is in the index will be returned.
470
585
        """
471
 
        # PERFORMANCE TODO: parse and bisect all remaining data at some
472
 
        # threshold of total-index processing/get calling layers that expect to
473
 
        # read the entire index to use the iter_all_entries  method instead.
474
586
        keys = set(keys)
475
587
        if not keys:
476
588
            return []
477
589
        if self._size is None and self._nodes is None:
478
590
            self._buffer_all()
 
591
 
 
592
        # We fit about 20 keys per minimum-read (4K), so if we are looking for
 
593
        # more than 1/20th of the index its likely (assuming homogenous key
 
594
        # spread) that we'll read the entire index. If we're going to do that,
 
595
        # buffer the whole thing. A better analysis might take key spread into
 
596
        # account - but B+Tree indices are better anyway.
 
597
        # We could look at all data read, and use a threshold there, which will
 
598
        # trigger on ancestry walks, but that is not yet fully mapped out.
 
599
        if self._nodes is None and len(keys) * 20 > self.key_count():
 
600
            self._buffer_all()
479
601
        if self._nodes is not None:
480
602
            return self._iter_entries_from_total_buffer(keys)
481
603
        else:
523
645
                else:
524
646
                    yield self, key, self._nodes[key]
525
647
            return
 
648
        nodes_by_key = self._get_nodes_by_key()
526
649
        for key in keys:
527
650
            # sanity check
528
651
            if key[0] is None:
530
653
            if len(key) != self._key_length:
531
654
                raise errors.BadIndexKey(key)
532
655
            # find what it refers to:
533
 
            key_dict = self._nodes_by_key
 
656
            key_dict = nodes_by_key
534
657
            elements = list(key)
535
658
            # find the subdict whose contents should be returned.
536
659
            try:
623
746
        if self._bisect_nodes is None:
624
747
            readv_ranges.append(_HEADER_READV)
625
748
        self._read_and_parse(readv_ranges)
 
749
        result = []
 
750
        if self._nodes is not None:
 
751
            # _read_and_parse triggered a _buffer_all because we requested the
 
752
            # whole data range
 
753
            for location, key in location_keys:
 
754
                if key not in self._nodes: # not present
 
755
                    result.append(((location, key), False))
 
756
                elif self.node_ref_lists:
 
757
                    value, refs = self._nodes[key]
 
758
                    result.append(((location, key),
 
759
                        (self, key, value, refs)))
 
760
                else:
 
761
                    result.append(((location, key),
 
762
                        (self, key, self._nodes[key])))
 
763
            return result
626
764
        # generate results:
627
765
        #  - figure out <, >, missing, present
628
766
        #  - result present references so we can return them.
629
 
        result = []
630
767
        # keys that we cannot answer until we resolve references
631
768
        pending_references = []
632
769
        pending_locations = set()
682
819
            if length > 0:
683
820
                readv_ranges.append((location, length))
684
821
        self._read_and_parse(readv_ranges)
 
822
        if self._nodes is not None:
 
823
            # The _read_and_parse triggered a _buffer_all, grab the data and
 
824
            # return it
 
825
            for location, key in pending_references:
 
826
                value, refs = self._nodes[key]
 
827
                result.append(((location, key), (self, key, value, refs)))
 
828
            return result
685
829
        for location, key in pending_references:
686
830
            # answer key references we had to look-up-late.
687
 
            index = self._parsed_key_index(key)
688
831
            value, refs = self._bisect_nodes[key]
689
832
            result.append(((location, key), (self, key,
690
833
                value, self._resolve_references(refs))))
883
1026
                raise errors.BadIndexData(self)
884
1027
            # keys are tuples. Each element is a string that may occur many
885
1028
            # times, so we intern them to save space. AB, RC, 200807
886
 
            key = tuple(intern(element) for element in elements[:self._key_length])
 
1029
            key = tuple([intern(element) for element in elements[:self._key_length]])
887
1030
            if first_key is None:
888
1031
                first_key = key
889
1032
            absent, references, value = elements[-3:]
960
1103
 
961
1104
        :param readv_ranges: A prepared readv range list.
962
1105
        """
963
 
        if readv_ranges:
964
 
            readv_data = self._transport.readv(self._name, readv_ranges, True,
965
 
                self._size)
966
 
            # parse
967
 
            for offset, data in readv_data:
968
 
                if self._bisect_nodes is None:
969
 
                    # this must be the start
970
 
                    if not (offset == 0):
971
 
                        raise AssertionError()
972
 
                    offset, data = self._parse_header_from_bytes(data)
973
 
                # print readv_ranges, "[%d:%d]" % (offset, offset + len(data))
974
 
                self._parse_region(offset, data)
 
1106
        if not readv_ranges:
 
1107
            return
 
1108
        if self._nodes is None and self._bytes_read * 2 >= self._size:
 
1109
            # We've already read more than 50% of the file and we are about to
 
1110
            # request more data, just _buffer_all() and be done
 
1111
            self._buffer_all()
 
1112
            return
 
1113
 
 
1114
        readv_data = self._transport.readv(self._name, readv_ranges, True,
 
1115
            self._size)
 
1116
        # parse
 
1117
        for offset, data in readv_data:
 
1118
            self._bytes_read += len(data)
 
1119
            if offset == 0 and len(data) == self._size:
 
1120
                # We read the whole range, most likely because the
 
1121
                # Transport upcast our readv ranges into one long request
 
1122
                # for enough total data to grab the whole index.
 
1123
                self._buffer_all(StringIO(data))
 
1124
                return
 
1125
            if self._bisect_nodes is None:
 
1126
                # this must be the start
 
1127
                if not (offset == 0):
 
1128
                    raise AssertionError()
 
1129
                offset, data = self._parse_header_from_bytes(data)
 
1130
            # print readv_ranges, "[%d:%d]" % (offset, offset + len(data))
 
1131
            self._parse_region(offset, data)
975
1132
 
976
1133
    def _signature(self):
977
1134
        """The file signature for this index type."""
997
1154
    in the index list.
998
1155
    """
999
1156
 
1000
 
    def __init__(self, indices):
 
1157
    def __init__(self, indices, reload_func=None):
1001
1158
        """Create a CombinedGraphIndex backed by indices.
1002
1159
 
1003
1160
        :param indices: An ordered list of indices to query for data.
 
1161
        :param reload_func: A function to call if we find we are missing an
 
1162
            index. Should have the form reload_func() => True/False to indicate
 
1163
            if reloading actually changed anything.
1004
1164
        """
1005
1165
        self._indices = indices
 
1166
        self._reload_func = reload_func
1006
1167
 
1007
1168
    def __repr__(self):
1008
1169
        return "%s(%s)" % (
1041
1202
            found_parents[key] = parents
1042
1203
        return found_parents
1043
1204
 
 
1205
    has_key = _has_key_from_parent_map
 
1206
 
1044
1207
    def insert_index(self, pos, index):
1045
1208
        """Insert a new index in the list of indices to query.
1046
1209
 
1060
1223
            the most efficient order for the index.
1061
1224
        """
1062
1225
        seen_keys = set()
1063
 
        for index in self._indices:
1064
 
            for node in index.iter_all_entries():
1065
 
                if node[1] not in seen_keys:
1066
 
                    yield node
1067
 
                    seen_keys.add(node[1])
 
1226
        while True:
 
1227
            try:
 
1228
                for index in self._indices:
 
1229
                    for node in index.iter_all_entries():
 
1230
                        if node[1] not in seen_keys:
 
1231
                            yield node
 
1232
                            seen_keys.add(node[1])
 
1233
                return
 
1234
            except errors.NoSuchFile:
 
1235
                self._reload_or_raise()
1068
1236
 
1069
1237
    def iter_entries(self, keys):
1070
1238
        """Iterate over keys within the index.
1078
1246
            efficient order for the index.
1079
1247
        """
1080
1248
        keys = set(keys)
1081
 
        for index in self._indices:
1082
 
            if not keys:
 
1249
        while True:
 
1250
            try:
 
1251
                for index in self._indices:
 
1252
                    if not keys:
 
1253
                        return
 
1254
                    for node in index.iter_entries(keys):
 
1255
                        keys.remove(node[1])
 
1256
                        yield node
1083
1257
                return
1084
 
            for node in index.iter_entries(keys):
1085
 
                keys.remove(node[1])
1086
 
                yield node
 
1258
            except errors.NoSuchFile:
 
1259
                self._reload_or_raise()
1087
1260
 
1088
1261
    def iter_entries_prefix(self, keys):
1089
1262
        """Iterate over keys within the index using prefix matching.
1109
1282
        if not keys:
1110
1283
            return
1111
1284
        seen_keys = set()
1112
 
        for index in self._indices:
1113
 
            for node in index.iter_entries_prefix(keys):
1114
 
                if node[1] in seen_keys:
1115
 
                    continue
1116
 
                seen_keys.add(node[1])
1117
 
                yield node
 
1285
        while True:
 
1286
            try:
 
1287
                for index in self._indices:
 
1288
                    for node in index.iter_entries_prefix(keys):
 
1289
                        if node[1] in seen_keys:
 
1290
                            continue
 
1291
                        seen_keys.add(node[1])
 
1292
                        yield node
 
1293
                return
 
1294
            except errors.NoSuchFile:
 
1295
                self._reload_or_raise()
1118
1296
 
1119
1297
    def key_count(self):
1120
1298
        """Return an estimate of the number of keys in this index.
1121
 
        
 
1299
 
1122
1300
        For CombinedGraphIndex this is approximated by the sum of the keys of
1123
1301
        the child indices. As child indices may have duplicate keys this can
1124
1302
        have a maximum error of the number of child indices * largest number of
1125
1303
        keys in any index.
1126
1304
        """
1127
 
        return sum((index.key_count() for index in self._indices), 0)
 
1305
        while True:
 
1306
            try:
 
1307
                return sum((index.key_count() for index in self._indices), 0)
 
1308
            except errors.NoSuchFile:
 
1309
                self._reload_or_raise()
 
1310
 
 
1311
    missing_keys = _missing_keys_from_parent_map
 
1312
 
 
1313
    def _reload_or_raise(self):
 
1314
        """We just got a NoSuchFile exception.
 
1315
 
 
1316
        Try to reload the indices, if it fails, just raise the current
 
1317
        exception.
 
1318
        """
 
1319
        if self._reload_func is None:
 
1320
            raise
 
1321
        exc_type, exc_value, exc_traceback = sys.exc_info()
 
1322
        trace.mutter('Trying to reload after getting exception: %s',
 
1323
                     exc_value)
 
1324
        if not self._reload_func():
 
1325
            # We tried to reload, but nothing changed, so we fail anyway
 
1326
            trace.mutter('_reload_func indicated nothing has changed.'
 
1327
                         ' Raising original exception.')
 
1328
            raise exc_type, exc_value, exc_traceback
1128
1329
 
1129
1330
    def validate(self):
1130
1331
        """Validate that everything in the index can be accessed."""
1131
 
        for index in self._indices:
1132
 
            index.validate()
 
1332
        while True:
 
1333
            try:
 
1334
                for index in self._indices:
 
1335
                    index.validate()
 
1336
                return
 
1337
            except errors.NoSuchFile:
 
1338
                self._reload_or_raise()
1133
1339
 
1134
1340
 
1135
1341
class InMemoryGraphIndex(GraphIndexBuilder):
1228
1434
                else:
1229
1435
                    yield self, key, node[2]
1230
1436
            return
 
1437
        nodes_by_key = self._get_nodes_by_key()
1231
1438
        for key in keys:
1232
1439
            # sanity check
1233
1440
            if key[0] is None:
1235
1442
            if len(key) != self._key_length:
1236
1443
                raise errors.BadIndexKey(key)
1237
1444
            # find what it refers to:
1238
 
            key_dict = self._nodes_by_key
 
1445
            key_dict = nodes_by_key
1239
1446
            elements = list(key)
1240
1447
            # find the subdict to return
1241
1448
            try: