1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """Jabber Service Discovery support.
19
20 Normative reference:
21 - `JEP 30 <http://www.jabber.org/jeps/jep-0030.html>`__
22 """
23
24 __docformat__="restructuredtext en"
25
26 import warnings
27
28 import libxml2
29
30 from pyxmpp.xmlextra import common_doc,common_root
31 from pyxmpp.jid import JID
32 from pyxmpp import cache
33
34 from pyxmpp.utils import to_utf8
35 from pyxmpp.objects import StanzaPayloadWrapperObject
36 from pyxmpp.exceptions import ProtocolError
37
38 DISCO_NS="http://jabber.org/protocol/disco"
39 DISCO_ITEMS_NS=DISCO_NS+"#items"
40 DISCO_INFO_NS=DISCO_NS+"#info"
41
43 """An item of disco#items reply.
44
45 :Ivariables:
46 - `jid`: the JID of the item.
47 - `node`: node name of the item.
48 - `name`: name of the item.
49 - `action`: action of the item.
50 - `disco`: the disco reply this is the part of.
51 - `xmlnode`: XML element describing the item.
52 :Types:
53 - `jid`: `JID`
54 - `node`: `unicode`
55 - `name`: `unicode`
56 - `action`: `unicode`
57 - `disco`: `DiscoItems`
58 - `xmlnode`: `libxml2.xmlNode`
59 """
60 - def __init__(self,disco,xmlnode_or_jid,node=None,name=None,action=None):
61 """Initialize an `DiscoItem` object.
62
63 :Parameters:
64 - `disco`: the disco#items reply `self` is a part of.
65 - `xmlnode_or_jid`: XML element describing the item or the JID of
66 the item.
67 - `node`: disco node of the item.
68 - `name`: name of the item.
69 - `action`: 'action' attribute of the item.
70 :Types:
71 - `disco`: `DiscoItems`
72 - `xmlnode_or_jid`: `libxml2.xmlNode` or `JID`
73 - `node`: `unicode`
74 - `name`: `unicode`
75 - `action`: `unicode`
76 """
77 self.disco=disco
78 if isinstance(xmlnode_or_jid,JID):
79 if disco:
80 self.xmlnode=disco.xmlnode.newChild(None,"item",None)
81 else:
82 self.xmlnode=common_root.newChild(None,"item",None)
83 ns=self.xmlnode.newNs(DISCO_ITEMS_NS,None)
84 self.xmlnode.setNs(ns)
85 self.set_jid(xmlnode_or_jid)
86 self.set_name(name)
87 self.set_node(node)
88 self.set_action(action)
89 else:
90 if disco is None:
91 self.xmlnode=xmlnode_or_jid.copyNode(1)
92 else:
93 self.xmlnode=xmlnode_or_jid
94 if name:
95 self.set_name(name)
96 if node:
97 self.set_node(node)
98 if action:
99 self.set_action(action)
100 self.xpath_ctxt=common_doc.xpathNewContext()
101 self.xpath_ctxt.setContextNode(self.xmlnode)
102 self.xpath_ctxt.xpathRegisterNs("d",DISCO_ITEMS_NS)
103
105 if self.disco is None:
106 if self.xmlnode:
107 self.xmlnode.unlinkNode()
108 self.xmlnode.freeNode()
109 self.xmlnode=None
110 if self.xpath_ctxt:
111 self.xpath_ctxt.xpathFreeContext()
112
115
117 """Remove `self` from the containing `DiscoItems` object."""
118 if self.disco is None:
119 return
120 self.xmlnode.unlinkNode()
121 oldns=self.xmlnode.ns()
122 ns=self.xmlnode.newNs(oldns.getContent(),None)
123 self.xmlnode.replaceNs(oldns,ns)
124 common_root.addChild(self.xmlnode())
125 self.disco=None
126
128 """Get the name of the item.
129
130 :return: the name of the item or `None`.
131 :returntype: `unicode`"""
132 name = self.xmlnode.prop("name")
133 if name is None:
134 return None
135 return name.decode("utf-8")
136
138 """Set the name of the item.
139
140 :Parameters:
141 - `name`: the new name or `None`.
142 :Types:
143 - `name`: `unicode` """
144 if name is None:
145 if self.xmlnode.hasProp("name"):
146 self.xmlnode.unsetProp("name")
147 return
148 name = unicode(name)
149 self.xmlnode.setProp("name", name.encode("utf-8"))
150
151 name = property(get_name, set_name)
152
154 """Get the node of the item.
155
156 :return: the node of the item or `None`.
157 :returntype: `unicode`"""
158 node = self.xmlnode.prop("node")
159 if node is None:
160 return None
161 return node.decode("utf-8")
162
164 """Set the node of the item.
165
166 :Parameters:
167 - `node`: the new node or `None`.
168 :Types:
169 - `node`: `unicode`
170 """
171 if node is None:
172 if self.xmlnode.hasProp("node"):
173 self.xmlnode.unsetProp("node")
174 return
175 node = unicode(node)
176 self.xmlnode.setProp("node", node.encode("utf-8"))
177
178 node = property(get_node, set_node)
179
181 """Get the action attribute of the item.
182
183 :return: the action of the item or `None`.
184 :returntype: `unicode`"""
185 action=self.xmlnode.prop("action")
186 if action is None:
187 return None
188 return action.decode("utf-8")
189
191 """Set the action of the item.
192
193 :Parameters:
194 - `action`: the new action or `None`.
195 :Types:
196 - `action`: `unicode`
197 """
198 if action is None:
199 if self.xmlnode.hasProp("action"):
200 self.xmlnode.unsetProp("action")
201 return
202 if action not in ("remove","update"):
203 raise ValueError, "Action must be 'update' or 'remove'"
204 action = unicode(action)
205 self.xmlnode.setProp("action", action.encode("utf-8"))
206
207 action = property(get_action, set_action)
208
210 """Get the JID of the item.
211
212 :return: the JID of the item.
213 :returntype: `JID`"""
214 jid = self.xmlnode.prop("jid")
215 return JID( jid.decode("utf-8") )
216
218 """Set the JID of the item.
219
220 :Parameters:
221 - `jid`: the new jid.
222 :Types:
223 - `jid`: `JID`
224 """
225 self.xmlnode.setProp("jid", jid.as_unicode().encode("utf-8"))
226
227 jid = property(get_jid, set_jid)
228
230 """An <identity/> element of disco#info reply.
231
232 Identifies an item by its name, category and type.
233
234 :Ivariables:
235 - `disco`: the disco reply this is the part of.
236 - `xmlnode`: XML element describing the identity.
237 :Types:
238 - `disco`: `DiscoInfo`
239 - `xmlnode`: `libxml2.xmlNode`
240 """
241 - def __init__(self, disco, xmlnode_or_name, item_category=None, item_type=None, replace=False):
242 """Initialize an `DiscoIdentity` object.
243
244 :Parameters:
245 - `disco`: the disco#info reply `self` is a part of.
246 - `xmlnode_or_name`: XML element describing the identity or the
247 name of the item described.
248 - `item_category`: category of the item described.
249 - `item_type`: type of the item described.
250 - `replace`: if `True` than all other <identity/> elements in
251 `disco` will be removed.
252 :Types:
253 - `disco`: `DiscoItems`
254 - `xmlnode_or_name`: `libxml2.xmlNode` or `unicode`
255 - `item_category`: `unicode`
256 - `item_type`: `unicode`
257 - `replace`: `bool`
258 """
259 self.disco=disco
260 if disco and replace:
261 old=disco.xpath_ctxt.xpathEval("d:identity")
262 if old:
263 for n in old:
264 n.unlinkNode()
265 n.freeNode()
266 if isinstance(xmlnode_or_name,libxml2.xmlNode):
267 if disco is None:
268 self.xmlnode=xmlnode_or_name.copyNode(1)
269 else:
270 self.xmlnode=xmlnode_or_name
271 elif not item_category:
272 raise ValueError,"DiscoInfo requires category"
273 elif not item_type:
274 raise ValueError,"DiscoInfo requires type"
275 else:
276 if disco:
277 self.xmlnode=disco.xmlnode.newChild(None,"identity",None)
278 else:
279 self.xmlnode=common_root.newChild(None,"identity",None)
280 ns=self.xmlnode.newNs(DISCO_INFO_NS,None)
281 self.xmlnode.setNs(ns)
282 self.set_name(xmlnode_or_name)
283 self.set_category(item_category)
284 self.set_type(item_type)
285 self.xpath_ctxt=common_doc.xpathNewContext()
286 self.xpath_ctxt.setContextNode(self.xmlnode)
287 self.xpath_ctxt.xpathRegisterNs("d",DISCO_INFO_NS)
288
290 if self.disco is None:
291 if self.xmlnode:
292 self.xmlnode.unlinkNode()
293 self.xmlnode.freeNode()
294 self.xmlnode=None
295 if self.xpath_ctxt:
296 self.xpath_ctxt.xpathFreeContext()
297
300
302 """Remove `self` from the containing `DiscoInfo` object."""
303 if self.disco is None:
304 return
305 self.xmlnode.unlinkNode()
306 oldns=self.xmlnode.ns()
307 ns=self.xmlnode.newNs(oldns.getContent(),None)
308 self.xmlnode.replaceNs(oldns,ns)
309 common_root.addChild(self.xmlnode())
310 self.disco=None
311
313 """Get the name of the item.
314
315 :return: the name of the item or `None`.
316 :returntype: `unicode`"""
317 var = self.xmlnode.prop("name")
318 if not var:
319 var = ""
320 return var.decode("utf-8")
321
323 """Set the name of the item.
324
325 :Parameters:
326 - `name`: the new name or `None`.
327 :Types:
328 - `name`: `unicode` """
329 if not name:
330 raise ValueError, "name is required in DiscoIdentity"
331 name = unicode(name)
332 self.xmlnode.setProp("name", name.encode("utf-8"))
333
334 name = property(get_name, set_name)
335
337 """Get the category of the item.
338
339 :return: the category of the item.
340 :returntype: `unicode`"""
341 var = self.xmlnode.prop("category")
342 if not var:
343 var = "?"
344 return var.decode("utf-8")
345
347 """Set the category of the item.
348
349 :Parameters:
350 - `category`: the new category.
351 :Types:
352 - `category`: `unicode` """
353 if not category:
354 raise ValueError, "Category is required in DiscoIdentity"
355 category = unicode(category)
356 self.xmlnode.setProp("category", category.encode("utf-8"))
357
358 category = property(get_category, set_category)
359
361 """Get the type of the item.
362
363 :return: the type of the item.
364 :returntype: `unicode`"""
365 item_type = self.xmlnode.prop("type")
366 if not item_type:
367 item_type = "?"
368 return item_type.decode("utf-8")
369
371 """Set the type of the item.
372
373 :Parameters:
374 - `item_type`: the new type.
375 :Types:
376 - `item_type`: `unicode` """
377 if not item_type:
378 raise ValueError,"Type is required in DiscoIdentity"
379 item_type = unicode(item_type)
380 self.xmlnode.setProp("type", item_type.encode("utf-8"))
381
382 type = property(get_type, set_type)
383
385 """A disco#items response or publish-request object.
386
387 :Ivariables:
388 - `node`: node name of the disco#items element.
389 - `items`: items in the disco#items element.
390 - `xmlnode`: XML element listing the items.
391 :Types:
392 - `node`: `unicode`
393 - `items`: `tuple` of `DiscoItem`
394 - `xmlnode`: `libxml2.xmlNode`
395 """
396 - def __init__(self,xmlnode_or_node=None):
397 """Initialize an `DiscoItems` object.
398
399 Wrap an existing disco#items XML element or create a new one.
400
401 :Parameters:
402 - `xmlnode_or_node`: XML node to be wrapped into `self` or an item
403 node name.
404 :Types:
405 - `xmlnode_or_node`: `libxml2.xmlNode` or `unicode`"""
406 self.xmlnode=None
407 self.xpath_ctxt=None
408 if isinstance(xmlnode_or_node,libxml2.xmlNode):
409 ns=xmlnode_or_node.ns()
410 if ns.getContent() != DISCO_ITEMS_NS:
411 raise ValueError, "Bad disco-items namespace"
412 self.xmlnode=xmlnode_or_node.docCopyNode(common_doc,1)
413 common_root.addChild(self.xmlnode)
414 self.ns=self.xmlnode.ns()
415 else:
416 self.xmlnode=common_root.newChild(None,"query",None)
417 self.ns=self.xmlnode.newNs(DISCO_ITEMS_NS,None)
418 self.xmlnode.setNs(self.ns)
419 self.set_node(xmlnode_or_node)
420 self.xpath_ctxt=common_doc.xpathNewContext()
421 self.xpath_ctxt.setContextNode(self.xmlnode)
422 self.xpath_ctxt.xpathRegisterNs("d",DISCO_ITEMS_NS)
423
425 if self.xmlnode:
426 self.xmlnode.unlinkNode()
427 self.xmlnode.freeNode()
428 self.xmlnode=None
429 if self.xpath_ctxt:
430 self.xpath_ctxt.xpathFreeContext()
431 self.xpath_ctxt=None
432
434 """Get the node address of the `DiscoItems` object.
435
436 :return: the node name.
437 :returntype: `unicode`"""
438 node = self.xmlnode.prop("node")
439 if not node:
440 return None
441 return node.decode("utf-8")
442
444 """Set the node of the disco#item element.
445
446 :Parameters:
447 - `node`: the new node or `None`.
448 :Types:
449 - `node`: `unicode`
450 """
451 if node is None:
452 if self.xmlnode.hasProp("node"):
453 self.xmlnode.unsetProp("node")
454 return
455 node = unicode(node)
456 self.xmlnode.setProp("node", node.encode("utf-8"))
457
458 node = property(get_node, set_node)
459
461 """Get the items contained in `self`.
462
463 :return: the items contained.
464 :returntype: `list` of `DiscoItem`"""
465 ret=[]
466 l=self.xpath_ctxt.xpathEval("d:item")
467 if l is not None:
468 for i in l:
469 ret.append(DiscoItem(self, i))
470 return ret
471
473 """Set items in the disco#items object.
474
475 All previous items are removed.
476
477 :Parameters:
478 - `item_list`: list of items or item properties
479 (jid,node,name,action).
480 :Types:
481 - `item_list`: sequence of `DiscoItem` or sequence of sequences
482 """
483 for item in self.items:
484 item.remove()
485 for item in item_list:
486 try:
487 self.add_item(item.jid,item.node,item.name,item.action)
488 except AttributeError:
489 self.add_item(*item)
490
491 items = property(get_items, set_items, doc = "List of `DiscoItems`")
492
494 """Clear cached item list."""
495 warnings.warn("DiscoItems.invalidate_items() is deprecated and not needed any more.", DeprecationWarning, stacklevel=1)
496
497 - def add_item(self,jid,node=None,name=None,action=None):
498 """Add a new item to the `DiscoItems` object.
499
500 :Parameters:
501 - `jid`: item JID.
502 - `node`: item node name.
503 - `name`: item name.
504 - `action`: action for a "disco push".
505 :Types:
506 - `jid`: `pyxmpp.JID`
507 - `node`: `unicode`
508 - `name`: `unicode`
509 - `action`: `unicode`
510
511 :returns: the item created.
512 :returntype: `DiscoItem`."""
513 return DiscoItem(self,jid,node,name,action)
514
516 """Check if `self` contains an item.
517
518 :Parameters:
519 - `jid`: JID of the item.
520 - `node`: node name of the item.
521 :Types:
522 - `jid`: `JID`
523 - `node`: `libxml2.xmlNode`
524
525 :return: `True` if the item is found in `self`.
526 :returntype: `bool`"""
527 l=self.xpath_ctxt.xpathEval("d:item")
528 if l is None:
529 return False
530 for it in l:
531 di=DiscoItem(self,it)
532 if di.jid==jid and di.node==node:
533 return True
534 return False
535
537 """A disco#info response object.
538
539 :Ivariables:
540 - `node`: node name of the disco#info element (cached).
541 - `identities`: identities in the disco#info object.
542 - `features`: features in the disco#info object.
543 - `xmlnode`: XML element listing the items.
544 :Types:
545 - `node`: `unicode`
546 - `identities`: `tuple` of `DiscoIdentity`
547 - `features`: `tuple` of `unicode`
548 - `xmlnode`: `libxml2.xmlNode`
549 """
550 - def __init__(self,xmlnode_or_node=None, parent=None, doc=None):
551 """Initialize an `DiscoInfo` object.
552
553 Wrap an existing disco#info XML element or create a new one.
554
555 :Parameters:
556 - `xmlnode_or_node`: XML node to be wrapped into `self` or an item
557 node name.
558 - `parent`: parent node for the `DiscoInfo` element.
559 - `doc`: document for the `DiscoInfo` element.
560 :Types:
561 - `xmlnode_or_node`: `libxml2.xmlNode` or `unicode`
562 - `parent`: `libxml2.xmlNode`
563 - `doc`: `libxml2.xmlDoc`
564 """
565 self.xmlnode=None
566 self.xpath_ctxt=None
567 if not doc:
568 doc=common_doc
569 if not parent:
570 parent=common_root
571 if isinstance(xmlnode_or_node,libxml2.xmlNode):
572 ns=xmlnode_or_node.ns()
573 if ns.getContent() != DISCO_INFO_NS:
574 raise ValueError, "Bad disco-info namespace"
575 self.xmlnode=xmlnode_or_node.docCopyNode(doc,1)
576 parent.addChild(self.xmlnode)
577 else:
578 self.xmlnode=parent.newChild(None,"query",None)
579 self.ns=self.xmlnode.newNs(DISCO_INFO_NS,None)
580 self.xmlnode.setNs(self.ns)
581 self.set_node(xmlnode_or_node)
582
583 self.xpath_ctxt=doc.xpathNewContext()
584 self.xpath_ctxt.setContextNode(self.xmlnode)
585 self.xpath_ctxt.xpathRegisterNs("d",DISCO_INFO_NS)
586
588 if self.xmlnode:
589 self.xmlnode.unlinkNode()
590 self.xmlnode.freeNode()
591 self.xmlnode=None
592 if self.xpath_ctxt:
593 self.xpath_ctxt.xpathFreeContext()
594 self.xpath_ctxt=None
595
597 """Get the node address of the `DiscoInfo` object.
598
599 :return: the node name.
600 :returntype: `unicode`"""
601
602 node=self.xmlnode.prop("node")
603 if not node:
604 return None
605 return node.decode("utf-8")
606
608 """Set the node of the disco#info element.
609
610 :Parameters:
611 - `node`: the new node or `None`.
612 :Types:
613 - `node`: `unicode`
614 """
615 if node is None:
616 if self.xmlnode.hasProp("node"):
617 self.xmlnode.unsetProp("node")
618 return
619 node = unicode(node)
620 self.xmlnode.setProp("node", node.encode("utf-8"))
621
622 node = property(get_node, set_node)
623
625 """Get the features contained in `self`.
626
627 :return: the list of features.
628 :returntype: `list` of `unicode`"""
629 l = self.xpath_ctxt.xpathEval("d:feature")
630 ret = []
631 for f in l:
632 if f.hasProp("var"):
633 ret.append( f.prop("var").decode("utf-8") )
634 return ret
635
637 """Set features in the disco#info object.
638
639 All existing features are removed from `self`.
640
641 :Parameters:
642 - `features`: list of features.
643 :Types:
644 - `features`: sequence of `unicode`
645 """
646 for var in self.features:
647 self.remove_feature(var)
648
649 for var in features:
650 self.add_feature(var)
651
652 features = property(get_features, set_features)
653
655 """Check if `self` contains the named feature.
656
657 :Parameters:
658 - `var`: the feature name.
659 :Types:
660 - `var`: `unicode`
661
662 :return: `True` if the feature is found in `self`.
663 :returntype: `bool`"""
664 if not var:
665 raise ValueError,"var is None"
666 if '"' not in var:
667 expr=u'd:feature[@var="%s"]' % (var,)
668 elif "'" not in var:
669 expr=u"d:feature[@var='%s']" % (var,)
670 else:
671 raise ValueError, "Invalid feature name"
672
673 l=self.xpath_ctxt.xpathEval(to_utf8(expr))
674 if l:
675 return True
676 else:
677 return False
678
680 """Clear cached feature list."""
681 warnings.warn("DiscoInfo.invalidate_features() is deprecated and not needed any more.", DeprecationWarning, stacklevel=1)
682
684 """Add a feature to `self`.
685
686 :Parameters:
687 - `var`: the feature name.
688 :Types:
689 - `var`: `unicode`"""
690 if self.has_feature(var):
691 return
692 n=self.xmlnode.newChild(None, "feature", None)
693 n.setProp("var", to_utf8(var))
694
696 """Remove a feature from `self`.
697
698 :Parameters:
699 - `var`: the feature name.
700 :Types:
701 - `var`: `unicode`"""
702 if not var:
703 raise ValueError,"var is None"
704 if '"' not in var:
705 expr='d:feature[@var="%s"]' % (var,)
706 elif "'" not in var:
707 expr="d:feature[@var='%s']" % (var,)
708 else:
709 raise ValueError, "Invalid feature name"
710
711 l=self.xpath_ctxt.xpathEval(expr)
712 if not l:
713 return
714
715 for f in l:
716 f.unlinkNode()
717 f.freeNode()
718
720 """List the identity objects contained in `self`.
721
722 :return: the list of identities.
723 :returntype: `list` of `DiscoIdentity`"""
724 ret=[]
725 l=self.xpath_ctxt.xpathEval("d:identity")
726 if l is not None:
727 for i in l:
728 ret.append(DiscoIdentity(self,i))
729 return ret
730
732 """Set identities in the disco#info object.
733
734 Remove all existing identities from `self`.
735
736 :Parameters:
737 - `identities`: list of identities or identity properties
738 (jid,node,category,type,name).
739 :Types:
740 - `identities`: sequence of `DiscoIdentity` or sequence of sequences
741 """
742 for identity in self.identities:
743 identity.remove()
744 for identity in identities:
745 try:
746 self.add_identity(identity.name,identity.category,identity.type)
747 except AttributeError:
748 self.add_identity(*identity)
749
750 identities = property(get_identities, set_identities)
751
753 """Check if the item described by `self` belongs to the given category
754 and type.
755
756 :Parameters:
757 - `item_category`: the category name.
758 - `item_type`: the type name. If `None` then only the category is
759 checked.
760 :Types:
761 - `item_category`: unicode
762 - `item_type`: unicode
763
764 :return: `True` if `self` contains at least one <identity/> object with
765 given type and category.
766 :returntype: `bool`"""
767 if not item_category:
768 raise ValueError, "bad category"
769 if not item_type:
770 type_expr=u""
771 elif '"' not in item_type:
772 type_expr=u' and @type="%s"' % (item_type,)
773 elif "'" not in type:
774 type_expr=u" and @type='%s'" % (item_type,)
775 else:
776 raise ValueError, "Invalid type name"
777 if '"' not in item_category:
778 expr=u'd:identity[@category="%s"%s]' % (item_category,type_expr)
779 elif "'" not in item_category:
780 expr=u"d:identity[@category='%s'%s]" % (item_category,type_expr)
781 else:
782 raise ValueError, "Invalid category name"
783
784 l=self.xpath_ctxt.xpathEval(to_utf8(expr))
785 if l:
786 return True
787 else:
788 return False
789
791 """Clear cached identity list."""
792 warnings.warn("DiscoInfo.invalidate_identities() is deprecated and not needed any more.", DeprecationWarning, stacklevel=1)
793
794 - def add_identity(self,item_name,item_category=None,item_type=None):
795 """Add an identity to the `DiscoInfo` object.
796
797 :Parameters:
798 - `item_name`: name of the item.
799 - `item_category`: category of the item.
800 - `item_type`: type of the item.
801 :Types:
802 - `item_name`: `unicode`
803 - `item_category`: `unicode`
804 - `item_type`: `unicode`
805
806 :returns: the identity created.
807 :returntype: `DiscoIdentity`"""
808 return DiscoIdentity(self,item_name,item_category,item_type)
809
811 """Base class for disco cache fetchers.
812
813 :Cvariables:
814 - `stream`: stream used by the fetcher.
815 - `disco_class`: disco class to be used (`DiscoInfo` or `DiscoItems`).
816 :Types:
817 - `stream`: `pyxmpp.stream.Stream`
818 - `disco_class`: `classobj`
819 """
820 stream=None
821 disco_class=None
832
834 """Handle successful disco response.
835
836 :Parameters:
837 - `stanza`: the stanza received.
838 :Types:
839 - `stanza`: `pyxmpp.stanza.Stanza`"""
840 try:
841 d=self.disco_class(stanza.get_query())
842 self.got_it(d)
843 except ValueError,e:
844 self.error(e)
845
858
860 """Handle disco timeout."""
861 pass
862
864 """Register Service Discovery cache fetchers into given
865 cache suite and using the stream provided.
866
867 :Parameters:
868 - `cache_suite`: the cache suite where the fetchers are to be
869 registered.
870 - `stream`: the stream to be used by the fetchers.
871 :Types:
872 - `cache_suite`: `cache.CacheSuite`
873 - `stream`: `pyxmpp.stream.Stream`
874 """
875 tmp=stream
876 class DiscoInfoCacheFetcher(DiscoCacheFetcherBase):
877 """Cache fetcher for DiscoInfo."""
878 stream=tmp
879 disco_class=DiscoInfo
880 class DiscoItemsCacheFetcher(DiscoCacheFetcherBase):
881 """Cache fetcher for DiscoItems."""
882 stream=tmp
883 disco_class=DiscoItems
884 cache_suite.register_fetcher(DiscoInfo,DiscoInfoCacheFetcher)
885 cache_suite.register_fetcher(DiscoItems,DiscoItemsCacheFetcher)
886
887
888