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