Bump to 1.3.1
[slixmpp.git] / sleekxmpp / stanza / iq.py
blob088de4c0f7c0cef2711f0a8f615afa16c7990480
1 """
2 SleekXMPP: The Sleek XMPP Library
3 Copyright (C) 2010 Nathanael C. Fritz
4 This file is part of SleekXMPP.
6 See the file LICENSE for copying permission.
7 """
9 from sleekxmpp.stanza.rootstanza import RootStanza
10 from sleekxmpp.xmlstream import StanzaBase, ET
11 from sleekxmpp.xmlstream.handler import Waiter, Callback
12 from sleekxmpp.xmlstream.matcher import MatchIDSender, MatcherId
13 from sleekxmpp.exceptions import IqTimeout, IqError
16 class Iq(RootStanza):
18 """
19 XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of
20 requesting and modifying information, similar to HTTP's GET and
21 POST methods.
23 Each <iq> stanza must have an 'id' value which associates the
24 stanza with the response stanza. XMPP entities must always
25 be given a response <iq> stanza with a type of 'result' after
26 sending a stanza of type 'get' or 'set'.
28 Most uses cases for <iq> stanzas will involve adding a <query>
29 element whose namespace indicates the type of information
30 desired. However, some custom XMPP applications use <iq> stanzas
31 as a carrier stanza for an application-specific protocol instead.
33 Example <iq> Stanzas:
34 <iq to="user@example.com" type="get" id="314">
35 <query xmlns="http://jabber.org/protocol/disco#items" />
36 </iq>
38 <iq to="user@localhost" type="result" id="17">
39 <query xmlns='jabber:iq:roster'>
40 <item jid='otheruser@example.net'
41 name='John Doe'
42 subscription='both'>
43 <group>Friends</group>
44 </item>
45 </query>
46 </iq>
48 Stanza Interface:
49 query -- The namespace of the <query> element if one exists.
51 Attributes:
52 types -- May be one of: get, set, result, or error.
54 Methods:
55 __init__ -- Overrides StanzaBase.__init__.
56 unhandled -- Send error if there are no handlers.
57 set_payload -- Overrides StanzaBase.set_payload.
58 set_query -- Add or modify a <query> element.
59 get_query -- Return the namespace of the <query> element.
60 del_query -- Remove the <query> element.
61 reply -- Overrides StanzaBase.reply
62 send -- Overrides StanzaBase.send
63 """
65 namespace = 'jabber:client'
66 name = 'iq'
67 interfaces = set(('type', 'to', 'from', 'id', 'query'))
68 types = set(('get', 'result', 'set', 'error'))
69 plugin_attrib = name
71 def __init__(self, *args, **kwargs):
72 """
73 Initialize a new <iq> stanza with an 'id' value.
75 Overrides StanzaBase.__init__.
76 """
77 StanzaBase.__init__(self, *args, **kwargs)
78 if self['id'] == '':
79 if self.stream is not None:
80 self['id'] = self.stream.new_id()
81 else:
82 self['id'] = '0'
84 def unhandled(self):
85 """
86 Send a feature-not-implemented error if the stanza is not handled.
88 Overrides StanzaBase.unhandled.
89 """
90 if self['type'] in ('get', 'set'):
91 self.reply()
92 self['error']['condition'] = 'feature-not-implemented'
93 self['error']['text'] = 'No handlers registered for this request.'
94 self.send()
96 def set_payload(self, value):
97 """
98 Set the XML contents of the <iq> stanza.
100 Arguments:
101 value -- An XML object to use as the <iq> stanza's contents
103 self.clear()
104 StanzaBase.set_payload(self, value)
105 return self
107 def set_query(self, value):
109 Add or modify a <query> element.
111 Query elements are differentiated by their namespace.
113 Arguments:
114 value -- The namespace of the <query> element.
116 query = self.xml.find("{%s}query" % value)
117 if query is None and value:
118 plugin = self.plugin_tag_map.get('{%s}query' % value, None)
119 if plugin:
120 self.enable(plugin.plugin_attrib)
121 else:
122 self.clear()
123 query = ET.Element("{%s}query" % value)
124 self.xml.append(query)
125 return self
127 def get_query(self):
128 """Return the namespace of the <query> element."""
129 for child in self.xml:
130 if child.tag.endswith('query'):
131 ns = child.tag.split('}')[0]
132 if '{' in ns:
133 ns = ns[1:]
134 return ns
135 return ''
137 def del_query(self):
138 """Remove the <query> element."""
139 for child in self.xml:
140 if child.tag.endswith('query'):
141 self.xml.remove(child)
142 return self
144 def reply(self, clear=True):
146 Send a reply <iq> stanza.
148 Overrides StanzaBase.reply
150 Sets the 'type' to 'result' in addition to the default
151 StanzaBase.reply behavior.
153 Arguments:
154 clear -- Indicates if existing content should be
155 removed before replying. Defaults to True.
157 self['type'] = 'result'
158 StanzaBase.reply(self, clear)
159 return self
161 def send(self, block=True, timeout=None, callback=None, now=False, timeout_callback=None):
163 Send an <iq> stanza over the XML stream.
165 The send call can optionally block until a response is received or
166 a timeout occurs. Be aware that using blocking in non-threaded event
167 handlers can drastically impact performance. Otherwise, a callback
168 handler can be provided that will be executed when the Iq stanza's
169 result reply is received. Be aware though that that the callback
170 handler will not be executed in its own thread.
172 Using both block and callback is not recommended, and only the
173 callback argument will be used in that case.
175 Overrides StanzaBase.send
177 Arguments:
178 block -- Specify if the send call will block until a response
179 is received, or a timeout occurs. Defaults to True.
180 timeout -- The length of time (in seconds) to wait for a response
181 before exiting the send call if blocking is used.
182 Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
183 callback -- Optional reference to a stream handler function. Will
184 be executed when a reply stanza is received.
185 now -- Indicates if the send queue should be skipped and send
186 the stanza immediately. Used during stream
187 initialization. Defaults to False.
188 timeout_callback -- Optional reference to a stream handler function.
189 Will be executed when the timeout expires before a
190 response has been received with the originally-sent IQ
191 stanza. Only called if there is a callback parameter
192 (and therefore are in async mode).
194 if timeout is None:
195 timeout = self.stream.response_timeout
197 if self.stream.session_bind_event.is_set():
198 matcher = MatchIDSender({
199 'id': self['id'],
200 'self': self.stream.boundjid,
201 'peer': self['to']
203 else:
204 matcher = MatcherId(self['id'])
206 if callback is not None and self['type'] in ('get', 'set'):
207 handler_name = 'IqCallback_%s' % self['id']
208 if timeout_callback:
209 self.callback = callback
210 self.timeout_callback = timeout_callback
211 self.stream.schedule('IqTimeout_%s' % self['id'],
212 timeout,
213 self._fire_timeout,
214 repeat=False)
215 handler = Callback(handler_name,
216 matcher,
217 self._handle_result,
218 once=True)
219 else:
220 handler = Callback(handler_name,
221 matcher,
222 callback,
223 once=True)
224 self.stream.register_handler(handler)
225 StanzaBase.send(self, now=now)
226 return handler_name
227 elif block and self['type'] in ('get', 'set'):
228 waitfor = Waiter('IqWait_%s' % self['id'], matcher)
229 self.stream.register_handler(waitfor)
230 StanzaBase.send(self, now=now)
231 result = waitfor.wait(timeout)
232 if not result:
233 raise IqTimeout(self)
234 if result['type'] == 'error':
235 raise IqError(result)
236 return result
237 else:
238 return StanzaBase.send(self, now=now)
240 def _handle_result(self, iq):
241 # we got the IQ, so don't fire the timeout
242 self.stream.scheduler.remove('IqTimeout_%s' % self['id'])
243 self.callback(iq)
245 def _fire_timeout(self):
246 # don't fire the handler for the IQ, if it finally does come in
247 self.stream.remove_handler('IqCallback_%s' % self['id'])
248 self.timeout_callback(self)
250 def _set_stanza_values(self, values):
252 Set multiple stanza interface values using a dictionary.
254 Stanza plugin values may be set usind nested dictionaries.
256 If the interface 'query' is given, then it will be set
257 last to avoid duplication of the <query /> element.
259 Overrides ElementBase._set_stanza_values.
261 Arguments:
262 values -- A dictionary mapping stanza interface with values.
263 Plugin interfaces may accept a nested dictionary that
264 will be used recursively.
266 query = values.get('query', '')
267 if query:
268 del values['query']
269 StanzaBase._set_stanza_values(self, values)
270 self['query'] = query
271 else:
272 StanzaBase._set_stanza_values(self, values)
273 return self
276 # To comply with PEP8, method names now use underscores.
277 # Deprecated method names are re-mapped for backwards compatibility.
278 Iq.setPayload = Iq.set_payload
279 Iq.getQuery = Iq.get_query
280 Iq.setQuery = Iq.set_query
281 Iq.delQuery = Iq.del_query