1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """Caching proxy for Jabber/XMPP objects.
19
20 This package provides facilities to retrieve and transparently cache
21 cachable objects like Service Discovery responses or e.g. client version
22 informations."""
23
24 __docformat__ = "restructuredtext en"
25
26 import threading
27 from datetime import datetime, timedelta
28
29 _state_values = {
30 'new': 0,
31 'fresh': 1,
32 'old': 2,
33 'stale': 3,
34 'purged': 4
35 };
36
37
38
39
41 """An item in a cache.
42
43 :Ivariables:
44 - `value`: item value (cached object).
45 - `address`: item address.
46 - `state`: current state.
47 - `state_value`: numerical value of the current state (lower number means
48 fresher item).
49 - `timestamp`: time when the object was created.
50 - `freshness_time`: time when the object stops being fresh.
51 - `expire_time`: time when the object expires.
52 - `purge_time`: time when the object should be purged. When 0 then
53 item will never be automaticaly purged.
54 - `_lock`: lock for thread safety.
55 :Types:
56 - `value`: `instance`
57 - `address`: any hashable
58 - `state`: `str`
59 - `state_value`: `int`
60 - `timestamp`: `datetime`
61 - `freshness_time`: `datetime`
62 - `expire_time`: `datetime`
63 - `purge_time`: `datetime`
64 - `_lock`: `threading.RLock`"""
65 __slots__ = ['value', 'address', 'state', 'timestamp', 'freshness_time',
66 'expire_time', 'purge_time', 'state_value', '_lock']
67 - def __init__(self, address, value, freshness_period, expiration_period,
68 purge_period, state = "new"):
69 """Initialize an CacheItem object.
70
71 :Parameters:
72 - `address`: item address.
73 - `value`: item value (cached object).
74 - `freshness_period`: time interval after which the object stops being fresh.
75 - `expiration_period`: time interval after which the object expires.
76 - `purge_period`: time interval after which the object should be purged. When 0 then
77 item will never be automaticaly purged.
78 - `state`: initial state.
79 :Types:
80 - `address`: any hashable
81 - `value`: `instance`
82 - `freshness_period`: `timedelta`
83 - `expiration_period`: `timedelta`
84 - `purge_period`: `timedelta`
85 - `state`: `str`"""
86 if freshness_period>expiration_period:
87 raise ValueError, "freshness_period greater then expiration_period"
88 if expiration_period>purge_period:
89 raise ValueError, "expiration_period greater then purge_period"
90 self.address = address
91 self.value = value
92 now = datetime.utcnow()
93 self.timestamp = now
94 self.freshness_time = now+freshness_period
95 self.expire_time = now+expiration_period
96 if purge_period:
97 self.purge_time = now+purge_period
98 else:
99 self.purge_time = datetime.max
100 self.state = state
101 self.state_value = _state_values[state]
102 self._lock = threading.RLock()
103
105 """Update current status of the item and compute time of the next
106 state change.
107
108 :return: the new state.
109 :returntype: `datetime`"""
110 self._lock.acquire()
111 try:
112 now = datetime.utcnow()
113 if self.state == 'new':
114 self.state = 'fresh'
115 if self.state == 'fresh':
116 if now > self.freshness_time:
117 self.state = 'old'
118 if self.state == 'old':
119 if now > self.expire_time:
120 self.state = 'stale'
121 if self.state == 'stale':
122 if now > self.purge_time:
123 self.state = 'purged'
124 self.state_value = _state_values[self.state]
125 return self.state
126 finally:
127 self._lock.release()
128
137
138 _hour = timedelta(hours = 1)
139
141 """Base class for cache object fetchers -- classes responsible for
142 retrieving objects from network.
143
144 An instance of a fetcher class is created for each object requested and
145 not found in the cache, then `fetch` method is called to initialize
146 the asynchronous retrieval process. Fetcher object's `got_it` method
147 should be called on a successfull retrieval and `error` otherwise.
148 `timeout` will be called when the request timeouts.
149
150 :Ivariables:
151 - `cache`: cache object which created this fetcher.
152 - `address`: requested item address.
153 - `timeout_time`: timeout time.
154 - `active`: `True` as long as the fetcher is active and requestor
155 expects one of the handlers to be called.
156 :Types:
157 - `cache`: `Cache`
158 - `address`: any hashable
159 - `timeout_time`: `datetime`
160 - `active`: `bool`
161 """
162 - def __init__(self, cache, address,
163 item_freshness_period, item_expiration_period, item_purge_period,
164 object_handler, error_handler, timeout_handler, timeout_period,
165 backup_state = None):
166 """Initialize an `CacheFetcher` object.
167
168 :Parameters:
169 - `cache`: cache object which created this fetcher.
170 - `address`: requested item address.
171 - `item_freshness_period`: freshness period for the requested item.
172 - `item_expiration_period`: expiration period for the requested item.
173 - `item_purge_period`: purge period for the requested item.
174 - `object_handler`: function to be called after the item is fetched.
175 - `error_handler`: function to be called on error.
176 - `timeout_handler`: function to be called on timeout
177 - `timeout_period`: timeout interval.
178 - `backup_state`: when not `None` and the fetch fails than an
179 object from cache of at least this state will be passed to the
180 `object_handler`. If such object is not available, then
181 `error_handler` is called.
182 :Types:
183 - `cache`: `Cache`
184 - `address`: any hashable
185 - `item_freshness_period`: `timedelta`
186 - `item_expiration_period`: `timedelta`
187 - `item_purge_period`: `timedelta`
188 - `object_handler`: callable(address, value, state)
189 - `error_handler`: callable(address, error_data)
190 - `timeout_handler`: callable(address)
191 - `timeout_period`: `timedelta`
192 - `backup_state`: `bool`"""
193 self.cache = cache
194 self.address = address
195 self._item_freshness_period = item_freshness_period
196 self._item_expiration_period = item_expiration_period
197 self._item_purge_period = item_purge_period
198 self._object_handler = object_handler
199 self._error_handler = error_handler
200 self._timeout_handler = timeout_handler
201 if timeout_period:
202 self.timeout_time = datetime.utcnow()+timeout_period
203 else:
204 self.timeout_time = datetime.max
205 self._backup_state = backup_state
206 self.active = True
207
213
215 """Mark the fetcher inactive after it is removed from the cache."""
216 self.active = False
217
219 """Start the retrieval process.
220
221 This method must be implemented in any fetcher class."""
222 raise RuntimeError, "Pure virtual method called"
223
224 - def got_it(self, value, state = "new"):
225 """Handle a successfull retrieval and call apriopriate handler.
226
227 Should be called when retrieval succeeds.
228
229 Do nothing when the fetcher is not active any more (after
230 one of handlers was already called).
231
232 :Parameters:
233 - `value`: fetched object.
234 - `state`: initial state of the object.
235 :Types:
236 - `value`: any
237 - `state`: `str`"""
238 if not self.active:
239 return
240 item = CacheItem(self.address, value, self._item_freshness_period,
241 self._item_expiration_period, self._item_purge_period, state)
242 self._object_handler(item.address, item.value, item.state)
243 self.cache.add_item(item)
244 self._deactivate()
245
246 - def error(self, error_data):
247 """Handle a retrieval error and call apriopriate handler.
248
249 Should be called when retrieval fails.
250
251 Do nothing when the fetcher is not active any more (after
252 one of handlers was already called).
253
254 :Parameters:
255 - `error_data`: additional information about the error (e.g. `StanzaError` instance).
256 :Types:
257 - `error_data`: fetcher dependant
258 """
259 if not self.active:
260 return
261 if not self._try_backup_item():
262 self._error_handler(self.address, error_data)
263 self.cache.invalidate_object(self.address)
264 self._deactivate()
265
267 """Handle fetcher timeout and call apriopriate handler.
268
269 Is called by the cache object and should _not_ be called by fetcher or
270 application.
271
272 Do nothing when the fetcher is not active any more (after
273 one of handlers was already called)."""
274 if not self.active:
275 return
276 if not self._try_backup_item():
277 if self._timeout_handler:
278 self._timeout_handler(self.address)
279 else:
280 self._error_handler(self.address, None)
281 self.cache.invalidate_object(self.address)
282 self._deactivate()
283
285 """Check if a backup item is available in cache and call
286 the item handler if it is.
287
288 :return: `True` if backup item was found.
289 :returntype: `bool`"""
290 if not self._backup_state:
291 return False
292 item = self.cache.get_item(self.address, self._backup_state)
293 if item:
294 self._object_handler(item.address, item.value, item.state)
295 return True
296 else:
297 False
298
300 """Caching proxy for object retrieval and caching.
301
302 Object factories ("fetchers") are registered in the `Cache` object and used
303 to e.g. retrieve requested objects from network. They are called only when
304 the requested object is not in the cache or is not fresh enough.
305
306 A state (freshness level) name may be provided when requesting an object.
307 When the cached item state is "less fresh" then requested, then new object
308 will be retrieved.
309
310 Following states are defined:
311
312 - 'new': always a new object should be retrieved.
313 - 'fresh': a fresh object (not older than freshness time)
314 - 'old': object not fresh, but most probably still valid.
315 - 'stale': object known to be expired.
316
317 :Ivariables:
318 - `default_freshness_period`: default freshness period (in seconds).
319 - `default_expiration_period`: default expiration period (in seconds).
320 - `default_purge_period`: default purge period (in seconds). When
321 0 then items are never purged because of their age.
322 - `max_items`: maximum number of items to store.
323 - `_items`: dictionary of stored items.
324 - `_items_list`: list of stored items with the most suitable for
325 purging first.
326 - `_fetcher`: fetcher class for this cache.
327 - `_active_fetchers`: list of active fetchers sorted by the time of
328 its expiration time.
329 - `_lock`: lock for thread safety.
330 :Types:
331 - `default_freshness_period`: timedelta
332 - `default_expiration_period`: timedelta
333 - `default_purge_period`: timedelta
334 - `max_items`: `int`
335 - `_items`: `dict` of (`classobj`, addr) -> `CacheItem`
336 - `_items_list`: `list` of (`int`, `datetime`, `CacheItem`)
337 - `_fetcher`: `CacheFetcher` based class
338 - `_active_fetchers`: `list` of (`int`, `CacheFetcher`)
339 - `_lock`: `threading.RLock`
340 """
341 - def __init__(self, max_items, default_freshness_period = _hour,
342 default_expiration_period = 12*_hour, default_purge_period = 24*_hour):
343 """Initialize a `Cache` object.
344
345 :Parameters:
346 - `default_freshness_period`: default freshness period (in seconds).
347 - `default_expiration_period`: default expiration period (in seconds).
348 - `default_purge_period`: default purge period (in seconds). When
349 0 then items are never purged because of their age.
350 - `max_items`: maximum number of items to store.
351 :Types:
352 - `default_freshness_period`: number
353 - `default_expiration_period`: number
354 - `default_purge_period`: number
355 - `max_items`: number
356 """
357 self.default_freshness_period = default_freshness_period
358 self.default_expiration_period = default_expiration_period
359 self.default_purge_period = default_purge_period
360 self.max_items = max_items
361 self._items = {}
362 self._items_list = []
363 self._fetcher = None
364 self._active_fetchers = []
365 self._purged = 0
366 self._lock = threading.RLock()
367
368 - def request_object(self, address, state, object_handler,
369 error_handler = None, timeout_handler = None,
370 backup_state = None, timeout = timedelta(minutes=60),
371 freshness_period = None, expiration_period = None,
372 purge_period = None):
373 """Request an object with given address and state not worse than
374 `state`. The object will be taken from cache if available, and
375 created/fetched otherwise. The request is asynchronous -- this metod
376 doesn't return the object directly, but the `object_handler` is called
377 as soon as the object is available (this may be before `request_object`
378 returns and may happen in other thread). On error the `error_handler`
379 will be called, and on timeout -- the `timeout_handler`.
380
381 :Parameters:
382 - `address`: address of the object requested.
383 - `state`: the worst acceptable object state. When 'new' then always
384 a new object will be created/fetched. 'stale' will select any
385 item available in cache.
386 - `object_handler`: function to be called when object is available.
387 It will be called with the following arguments: address, object
388 and its state.
389 - `error_handler`: function to be called on object retrieval error.
390 It will be called with two arguments: requested address and
391 additional error information (fetcher-specific, may be
392 StanzaError for XMPP objects). If not given, then the object
393 handler will be called with object set to `None` and state
394 "error".
395 - `timeout_handler`: function to be called on object retrieval
396 timeout. It will be called with only one argument: the requested
397 address. If not given, then the `error_handler` will be called
398 instead, with error details set to `None`.
399 - `backup_state`: when set and object in state `state` is not
400 available in the cache and object retrieval failed then object
401 with this state will also be looked-up in the cache and provided
402 if available.
403 - `timeout`: time interval after which retrieval of the object
404 should be given up.
405 - `freshness_period`: time interval after which the item created
406 should become 'old'.
407 - `expiration_period`: time interval after which the item created
408 should become 'stale'.
409 - `purge_period`: time interval after which the item created
410 shuld be removed from the cache.
411 :Types:
412 - `address`: any hashable
413 - `state`: "new", "fresh", "old" or "stale"
414 - `object_handler`: callable(address, value, state)
415 - `error_handler`: callable(address, error_data)
416 - `timeout_handler`: callable(address)
417 - `backup_state`: "new", "fresh", "old" or "stale"
418 - `timeout`: `timedelta`
419 - `freshness_period`: `timedelta`
420 - `expiration_period`: `timedelta`
421 - `purge_period`: `timedelta`
422 """
423 self._lock.acquire()
424 try:
425 if state == 'stale':
426 state = 'purged'
427 item = self.get_item(address, state)
428 if item:
429 object_handler(item.address, item.value, item.state)
430 return
431 if not self._fetcher:
432 raise TypeError, "No cache fetcher defined"
433 if not error_handler:
434 def default_error_handler(address, _unused):
435 "Default error handler."
436 return object_handler(address, None, 'error')
437 error_handler = default_error_handler
438 if not timeout_handler:
439 def default_timeout_handler(address):
440 "Default timeout handler."
441 return error_handler(address, None)
442 timeout_handler = default_timeout_handler
443 if freshness_period is None:
444 freshness_period = self.default_freshness_period
445 if expiration_period is None:
446 expiration_period = self.default_expiration_period
447 if purge_period is None:
448 purge_period = self.default_purge_period
449
450 fetcher = self._fetcher(self, address, freshness_period,
451 expiration_period, purge_period, object_handler, error_handler,
452 timeout_handler, timeout, backup_state)
453 fetcher.fetch()
454 self._active_fetchers.append((fetcher.timeout_time,fetcher))
455 self._active_fetchers.sort()
456 finally:
457 self._lock.release()
458
460 """Force cache item state change (to 'worse' state only).
461
462 :Parameters:
463 - `state`: the new state requested.
464 :Types:
465 - `state`: `str`"""
466 self._lock.acquire()
467 try:
468 item = self.get_item(address)
469 if item and item.state_value<_state_values[state]:
470 item.state=state
471 item.update_state()
472 self._items_list.sort()
473 finally:
474 self._lock.release()
475
477 """Add an item to the cache.
478
479 Item state is updated before adding it (it will not be 'new' any more).
480
481 :Parameters:
482 - `item`: the item to add.
483 :Types:
484 - `item`: `CacheItem`
485
486 :return: state of the item after addition.
487 :returntype: `str`
488 """
489 self._lock.acquire()
490 try:
491 state = item.update_state()
492 if state != 'purged':
493 if len(self._items_list) >= self.max_items:
494 self.purge_items()
495 self._items[item.address] = item
496 self._items_list.append(item)
497 self._items_list.sort()
498 return item.state
499 finally:
500 self._lock.release()
501
502 - def get_item(self, address, state = 'fresh'):
503 """Get an item from the cache.
504
505 :Parameters:
506 - `address`: its address.
507 - `state`: the worst state that is acceptable.
508 :Types:
509 - `address`: any hashable
510 - `state`: `str`
511
512 :return: the item or `None` if it was not found.
513 :returntype: `CacheItem`"""
514 self._lock.acquire()
515 try:
516 item = self._items.get(address)
517 if not item:
518 return None
519 self.update_item(item)
520 if _state_values[state] >= item.state_value:
521 return item
522 return None
523 finally:
524 self._lock.release()
525
527 """Update state of an item in the cache.
528
529 Update item's state and remove the item from the cache
530 if its new state is 'purged'
531
532 :Parameters:
533 - `item`: item to update.
534 :Types:
535 - `item`: `CacheItem`
536
537 :return: new state of the item.
538 :returntype: `str`"""
539
540 self._lock.acquire()
541 try:
542 state = item.update_state()
543 self._items_list.sort()
544 if item.state == 'purged':
545 self._purged += 1
546 if self._purged > 0.25*self.max_items:
547 self.purge_items()
548 return state
549 finally:
550 self._lock.release()
551
553 """Get the number of items in the cache.
554
555 :return: number of items.
556 :returntype: `int`"""
557 return len(self._items_list)
558
560 """Remove purged and overlimit items from the cache.
561
562 TODO: optimize somehow.
563
564 Leave no more than 75% of `self.max_items` items in the cache."""
565 self._lock.acquire()
566 try:
567 il=self._items_list
568 num_items = len(il)
569 need_remove = num_items - int(0.75 * self.max_items)
570
571 for _unused in range(need_remove):
572 item=il.pop(0)
573 try:
574 del self._items[item.address]
575 except KeyError:
576 pass
577
578 while il and il[0].update_state()=="purged":
579 item=il.pop(0)
580 try:
581 del self._items[item.address]
582 except KeyError:
583 pass
584 finally:
585 self._lock.release()
586
588 """Do the regular cache maintenance.
589
590 Must be called from time to time for timeouts and cache old items
591 purging to work."""
592 self._lock.acquire()
593 try:
594 now = datetime.utcnow()
595 for t,f in list(self._active_fetchers):
596 if t > now:
597 break
598 f.timeout()
599 self.purge_items()
600 finally:
601 self._lock.release()
602
604 """Remove a running fetcher from the list of active fetchers.
605
606 :Parameters:
607 - `fetcher`: fetcher instance.
608 :Types:
609 - `fetcher`: `CacheFetcher`"""
610 self._lock.acquire()
611 try:
612 for t, f in list(self._active_fetchers):
613 if f is fetcher:
614 self._active_fetchers.remove((t, f))
615 f._deactivated()
616 return
617 finally:
618 self._lock.release()
619
621 """Set the fetcher class.
622
623 :Parameters:
624 - `fetcher_class`: the fetcher class.
625 :Types:
626 - `fetcher_class`: `CacheFetcher` based class
627 """
628 self._lock.acquire()
629 try:
630 self._fetcher = fetcher_class
631 finally:
632 self._lock.release()
633
635 """Caching proxy for object retrieval and caching.
636
637 Object factories for other classes are registered in the
638 `Cache` object and used to e.g. retrieve requested objects from network.
639 They are called only when the requested object is not in the cache
640 or is not fresh enough.
641
642 Objects are addressed using their class and a class dependant address.
643 Eg. `pyxmpp.jabber.disco.DiscoInfo` objects are addressed using
644 (`pyxmpp.jabber.disco.DiscoInfo`,(jid, node)) tuple.
645
646 Additionaly a state (freshness level) name may be provided when requesting
647 an object. When the cached item state is "less fresh" then requested, then
648 new object will be retrieved.
649
650 Following states are defined:
651
652 - 'new': always a new object should be retrieved.
653 - 'fresh': a fresh object (not older than freshness time)
654 - 'old': object not fresh, but most probably still valid.
655 - 'stale': object known to be expired.
656
657 :Ivariables:
658 - `default_freshness_period`: default freshness period (in seconds).
659 - `default_expiration_period`: default expiration period (in seconds).
660 - `default_purge_period`: default purge period (in seconds). When
661 0 then items are never purged because of their age.
662 - `max_items`: maximum number of obejects of one class to store.
663 - `_caches`: dictionary of per-class caches.
664 - `_lock`: lock for thread safety.
665 :Types:
666 - `default_freshness_period`: timedelta
667 - `default_expiration_period`: timedelta
668 - `default_purge_period`: timedelta
669 - `max_items`: `int`
670 - `_caches`: `dict` of (`classobj`, addr) -> `Cache`
671 - `_lock`: `threading.RLock`
672 """
673 - def __init__(self, max_items, default_freshness_period = _hour,
674 default_expiration_period = 12*_hour, default_purge_period = 24*_hour):
675 """Initialize a `Cache` object.
676
677 :Parameters:
678 - `default_freshness_period`: default freshness period (in seconds).
679 - `default_expiration_period`: default expiration period (in seconds).
680 - `default_purge_period`: default purge period (in seconds). When
681 0 then items are never purged because of their age.
682 - `max_items`: maximum number of items to store.
683 :Types:
684 - `default_freshness_period`: number
685 - `default_expiration_period`: number
686 - `default_purge_period`: number
687 - `max_items`: number
688 """
689 self.default_freshness_period = default_freshness_period
690 self.default_expiration_period = default_expiration_period
691 self.default_purge_period = default_purge_period
692 self.max_items = max_items
693 self._caches = {}
694 self._lock = threading.RLock()
695
696 - def request_object(self, object_class, address, state, object_handler,
697 error_handler = None, timeout_handler = None,
698 backup_state = None, timeout = None,
699 freshness_period = None, expiration_period = None, purge_period = None):
700 """Request an object of given class, with given address and state not
701 worse than `state`. The object will be taken from cache if available,
702 and created/fetched otherwise. The request is asynchronous -- this
703 metod doesn't return the object directly, but the `object_handler` is
704 called as soon as the object is available (this may be before
705 `request_object` returns and may happen in other thread). On error the
706 `error_handler` will be called, and on timeout -- the
707 `timeout_handler`.
708
709 :Parameters:
710 - `object_class`: class (type) of the object requested.
711 - `address`: address of the object requested.
712 - `state`: the worst acceptable object state. When 'new' then always
713 a new object will be created/fetched. 'stale' will select any
714 item available in cache.
715 - `object_handler`: function to be called when object is available.
716 It will be called with the following arguments: address, object
717 and its state.
718 - `error_handler`: function to be called on object retrieval error.
719 It will be called with two arguments: requested address and
720 additional error information (fetcher-specific, may be
721 StanzaError for XMPP objects). If not given, then the object
722 handler will be called with object set to `None` and state
723 "error".
724 - `timeout_handler`: function to be called on object retrieval
725 timeout. It will be called with only one argument: the requested
726 address. If not given, then the `error_handler` will be called
727 instead, with error details set to `None`.
728 - `backup_state`: when set and object in state `state` is not
729 available in the cache and object retrieval failed then object
730 with this state will also be looked-up in the cache and provided
731 if available.
732 - `timeout`: time interval after which retrieval of the object
733 should be given up.
734 - `freshness_period`: time interval after which the item created
735 should become 'old'.
736 - `expiration_period`: time interval after which the item created
737 should become 'stale'.
738 - `purge_period`: time interval after which the item created
739 shuld be removed from the cache.
740 :Types:
741 - `object_class`: `classobj`
742 - `address`: any hashable
743 - `state`: "new", "fresh", "old" or "stale"
744 - `object_handler`: callable(address, value, state)
745 - `error_handler`: callable(address, error_data)
746 - `timeout_handler`: callable(address)
747 - `backup_state`: "new", "fresh", "old" or "stale"
748 - `timeout`: `timedelta`
749 - `freshness_period`: `timedelta`
750 - `expiration_period`: `timedelta`
751 - `purge_period`: `timedelta`
752 """
753
754 self._lock.acquire()
755 try:
756 if object_class not in self._caches:
757 raise TypeError, "No cache for %r" % (object_class,)
758
759 self._caches[object_class].request_object(address, state, object_handler,
760 error_handler, timeout_handler, backup_state, timeout,
761 freshness_period, expiration_period, purge_period)
762 finally:
763 self._lock.release()
764
766 """Do the regular cache maintenance.
767
768 Must be called from time to time for timeouts and cache old items
769 purging to work."""
770 self._lock.acquire()
771 try:
772 for cache in self._caches.values():
773 cache.tick()
774 finally:
775 self._lock.release()
776
778 """Register a fetcher class for an object class.
779
780 :Parameters:
781 - `object_class`: class to be retrieved by the fetcher.
782 - `fetcher_class`: the fetcher class.
783 :Types:
784 - `object_class`: `classobj`
785 - `fetcher_class`: `CacheFetcher` based class
786 """
787 self._lock.acquire()
788 try:
789 cache = self._caches.get(object_class)
790 if not cache:
791 cache = Cache(self.max_items, self.default_freshness_period,
792 self.default_expiration_period, self.default_purge_period)
793 self._caches[object_class] = cache
794 cache.set_fetcher(fetcher_class)
795 finally:
796 self._lock.release()
797
799 """Unregister a fetcher class for an object class.
800
801 :Parameters:
802 - `object_class`: class retrieved by the fetcher.
803 :Types:
804 - `object_class`: `classobj`
805 """
806 self._lock.acquire()
807 try:
808 cache = self._caches.get(object_class)
809 if not cache:
810 return
811 cache.set_fetcher(None)
812 finally:
813 self._lock.release()
814
815
816