stanzabase: types
This commit is contained in:
parent
79f71ec0c1
commit
62e66e7d03
1 changed files with 219 additions and 151 deletions
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
# slixmpp.xmlstream.stanzabase
|
# slixmpp.xmlstream.stanzabase
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
# module implements a wrapper layer for XML objects
|
# module implements a wrapper layer for XML objects
|
||||||
|
@ -11,13 +10,34 @@ from __future__ import annotations
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import weakref
|
import weakref
|
||||||
from typing import Optional
|
from typing import (
|
||||||
|
cast,
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
ClassVar,
|
||||||
|
Coroutine,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Iterable,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
Tuple,
|
||||||
|
Type,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
from weakref import ReferenceType
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
|
from slixmpp.types import JidStr
|
||||||
from slixmpp.xmlstream import JID
|
from slixmpp.xmlstream import JID
|
||||||
from slixmpp.xmlstream.tostring import tostring
|
from slixmpp.xmlstream.tostring import tostring
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from slixmpp.xmlstream import XMLStream
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +48,8 @@ XML_TYPE = type(ET.Element('xml'))
|
||||||
XML_NS = 'http://www.w3.org/XML/1998/namespace'
|
XML_NS = 'http://www.w3.org/XML/1998/namespace'
|
||||||
|
|
||||||
|
|
||||||
def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
|
def register_stanza_plugin(stanza: Type[ElementBase], plugin: Type[ElementBase],
|
||||||
|
iterable: bool = False, overrides: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Associate a stanza object as a plugin for another stanza.
|
Associate a stanza object as a plugin for another stanza.
|
||||||
|
|
||||||
|
@ -85,15 +106,15 @@ def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
|
||||||
stanza.plugin_overrides[interface] = plugin.plugin_attrib
|
stanza.plugin_overrides[interface] = plugin.plugin_attrib
|
||||||
|
|
||||||
|
|
||||||
def multifactory(stanza, plugin_attrib):
|
def multifactory(stanza: Type[ElementBase], plugin_attrib: str) -> Type[ElementBase]:
|
||||||
"""
|
"""
|
||||||
Returns a ElementBase class for handling reoccuring child stanzas
|
Returns a ElementBase class for handling reoccuring child stanzas
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def plugin_filter(self):
|
def plugin_filter(self: Multi) -> Callable[..., bool]:
|
||||||
return lambda x: isinstance(x, self._multistanza)
|
return lambda x: isinstance(x, self._multistanza)
|
||||||
|
|
||||||
def plugin_lang_filter(self, lang):
|
def plugin_lang_filter(self: Multi, lang: Optional[str]) -> Callable[..., bool]:
|
||||||
return lambda x: isinstance(x, self._multistanza) and \
|
return lambda x: isinstance(x, self._multistanza) and \
|
||||||
x['lang'] == lang
|
x['lang'] == lang
|
||||||
|
|
||||||
|
@ -101,31 +122,41 @@ def multifactory(stanza, plugin_attrib):
|
||||||
"""
|
"""
|
||||||
Template class for multifactory
|
Template class for multifactory
|
||||||
"""
|
"""
|
||||||
def setup(self, xml=None):
|
_multistanza: Type[ElementBase]
|
||||||
self.xml = ET.Element('')
|
|
||||||
|
|
||||||
def get_multi(self, lang=None):
|
def setup(self, xml: Optional[ET.Element] = None) -> bool:
|
||||||
parent = self.parent()
|
self.xml = ET.Element('')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_multi(self: Multi, lang: Optional[str] = None) -> List[ElementBase]:
|
||||||
|
parent = fail_without_parent(self)
|
||||||
if not lang or lang == '*':
|
if not lang or lang == '*':
|
||||||
res = filter(plugin_filter(self), parent)
|
res = filter(plugin_filter(self), parent)
|
||||||
else:
|
else:
|
||||||
res = filter(plugin_filter(self, lang), parent)
|
res = filter(plugin_lang_filter(self, lang), parent)
|
||||||
return list(res)
|
return list(res)
|
||||||
|
|
||||||
def set_multi(self, val, lang=None):
|
def set_multi(self: Multi, val: Iterable[ElementBase], lang: Optional[str] = None) -> None:
|
||||||
parent = self.parent()
|
parent = fail_without_parent(self)
|
||||||
del_multi = getattr(self, 'del_%s' % plugin_attrib)
|
del_multi = getattr(self, 'del_%s' % plugin_attrib)
|
||||||
del_multi(lang)
|
del_multi(lang)
|
||||||
for sub in val:
|
for sub in val:
|
||||||
parent.append(sub)
|
parent.append(sub)
|
||||||
|
|
||||||
def del_multi(self, lang=None):
|
def fail_without_parent(self: Multi) -> ElementBase:
|
||||||
parent = self.parent()
|
parent = None
|
||||||
|
if self.parent:
|
||||||
|
parent = self.parent()
|
||||||
|
if not parent:
|
||||||
|
raise ValueError('No stanza parent for multifactory')
|
||||||
|
return parent
|
||||||
|
|
||||||
|
def del_multi(self: Multi, lang: Optional[str] = None) -> None:
|
||||||
|
parent = fail_without_parent(self)
|
||||||
if not lang or lang == '*':
|
if not lang or lang == '*':
|
||||||
res = filter(plugin_filter(self), parent)
|
res = list(filter(plugin_filter(self), parent))
|
||||||
else:
|
else:
|
||||||
res = filter(plugin_filter(self, lang), parent)
|
res = list(filter(plugin_lang_filter(self, lang), parent))
|
||||||
res = list(res)
|
|
||||||
if not res:
|
if not res:
|
||||||
del parent.plugins[(plugin_attrib, None)]
|
del parent.plugins[(plugin_attrib, None)]
|
||||||
parent.loaded_plugins.remove(plugin_attrib)
|
parent.loaded_plugins.remove(plugin_attrib)
|
||||||
|
@ -149,7 +180,8 @@ def multifactory(stanza, plugin_attrib):
|
||||||
return Multi
|
return Multi
|
||||||
|
|
||||||
|
|
||||||
def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
|
def fix_ns(xpath: str, split: bool = False, propagate_ns: bool = True,
|
||||||
|
default_ns: str = '') -> Union[str, List[str]]:
|
||||||
"""Apply the stanza's namespace to elements in an XPath expression.
|
"""Apply the stanza's namespace to elements in an XPath expression.
|
||||||
|
|
||||||
:param string xpath: The XPath expression to fix with namespaces.
|
:param string xpath: The XPath expression to fix with namespaces.
|
||||||
|
@ -275,12 +307,12 @@ class ElementBase(object):
|
||||||
#: The XML tag name of the element, not including any namespace
|
#: The XML tag name of the element, not including any namespace
|
||||||
#: prefixes. For example, an :class:`ElementBase` object for
|
#: prefixes. For example, an :class:`ElementBase` object for
|
||||||
#: ``<message />`` would use ``name = 'message'``.
|
#: ``<message />`` would use ``name = 'message'``.
|
||||||
name = 'stanza'
|
name: ClassVar[str] = 'stanza'
|
||||||
|
|
||||||
#: The XML namespace for the element. Given ``<foo xmlns="bar" />``,
|
#: The XML namespace for the element. Given ``<foo xmlns="bar" />``,
|
||||||
#: then ``namespace = "bar"`` should be used. The default namespace
|
#: then ``namespace = "bar"`` should be used. The default namespace
|
||||||
#: is ``jabber:client`` since this is being used in an XMPP library.
|
#: is ``jabber:client`` since this is being used in an XMPP library.
|
||||||
namespace = 'jabber:client'
|
namespace: str = 'jabber:client'
|
||||||
|
|
||||||
#: For :class:`ElementBase` subclasses which are intended to be used
|
#: For :class:`ElementBase` subclasses which are intended to be used
|
||||||
#: as plugins, the ``plugin_attrib`` value defines the plugin name.
|
#: as plugins, the ``plugin_attrib`` value defines the plugin name.
|
||||||
|
@ -290,7 +322,7 @@ class ElementBase(object):
|
||||||
#: register_stanza_plugin(Message, FooPlugin)
|
#: register_stanza_plugin(Message, FooPlugin)
|
||||||
#: msg = Message()
|
#: msg = Message()
|
||||||
#: msg['foo']['an_interface_from_the_foo_plugin']
|
#: msg['foo']['an_interface_from_the_foo_plugin']
|
||||||
plugin_attrib = 'plugin'
|
plugin_attrib: ClassVar[str] = 'plugin'
|
||||||
|
|
||||||
#: For :class:`ElementBase` subclasses that are intended to be an
|
#: For :class:`ElementBase` subclasses that are intended to be an
|
||||||
#: iterable group of items, the ``plugin_multi_attrib`` value defines
|
#: iterable group of items, the ``plugin_multi_attrib`` value defines
|
||||||
|
@ -300,29 +332,29 @@ class ElementBase(object):
|
||||||
#: # Given stanza class Foo, with plugin_multi_attrib = 'foos'
|
#: # Given stanza class Foo, with plugin_multi_attrib = 'foos'
|
||||||
#: parent['foos']
|
#: parent['foos']
|
||||||
#: filter(isinstance(item, Foo), parent['substanzas'])
|
#: filter(isinstance(item, Foo), parent['substanzas'])
|
||||||
plugin_multi_attrib = ''
|
plugin_multi_attrib: ClassVar[str] = ''
|
||||||
|
|
||||||
#: The set of keys that the stanza provides for accessing and
|
#: The set of keys that the stanza provides for accessing and
|
||||||
#: manipulating the underlying XML object. This set may be augmented
|
#: manipulating the underlying XML object. This set may be augmented
|
||||||
#: with the :attr:`plugin_attrib` value of any registered
|
#: with the :attr:`plugin_attrib` value of any registered
|
||||||
#: stanza plugins.
|
#: stanza plugins.
|
||||||
interfaces = {'type', 'to', 'from', 'id', 'payload'}
|
interfaces: ClassVar[Set[str]] = {'type', 'to', 'from', 'id', 'payload'}
|
||||||
|
|
||||||
#: A subset of :attr:`interfaces` which maps interfaces to direct
|
#: A subset of :attr:`interfaces` which maps interfaces to direct
|
||||||
#: subelements of the underlying XML object. Using this set, the text
|
#: subelements of the underlying XML object. Using this set, the text
|
||||||
#: of these subelements may be set, retrieved, or removed without
|
#: of these subelements may be set, retrieved, or removed without
|
||||||
#: needing to define custom methods.
|
#: needing to define custom methods.
|
||||||
sub_interfaces = set()
|
sub_interfaces: ClassVar[Set[str]] = set()
|
||||||
|
|
||||||
#: A subset of :attr:`interfaces` which maps the presence of
|
#: A subset of :attr:`interfaces` which maps the presence of
|
||||||
#: subelements to boolean values. Using this set allows for quickly
|
#: subelements to boolean values. Using this set allows for quickly
|
||||||
#: checking for the existence of empty subelements like ``<required />``.
|
#: checking for the existence of empty subelements like ``<required />``.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 1.1
|
#: .. versionadded:: 1.1
|
||||||
bool_interfaces = set()
|
bool_interfaces: ClassVar[Set[str]] = set()
|
||||||
|
|
||||||
#: .. versionadded:: 1.1.2
|
#: .. versionadded:: 1.1.2
|
||||||
lang_interfaces = set()
|
lang_interfaces: ClassVar[Set[str]] = set()
|
||||||
|
|
||||||
#: In some cases you may wish to override the behaviour of one of the
|
#: In some cases you may wish to override the behaviour of one of the
|
||||||
#: parent stanza's interfaces. The ``overrides`` list specifies the
|
#: parent stanza's interfaces. The ``overrides`` list specifies the
|
||||||
|
@ -336,7 +368,7 @@ class ElementBase(object):
|
||||||
#: be affected.
|
#: be affected.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 1.0-Beta5
|
#: .. versionadded:: 1.0-Beta5
|
||||||
overrides = []
|
overrides: ClassVar[List[str]] = []
|
||||||
|
|
||||||
#: If you need to add a new interface to an existing stanza, you
|
#: If you need to add a new interface to an existing stanza, you
|
||||||
#: can create a plugin and set ``is_extension = True``. Be sure
|
#: can create a plugin and set ``is_extension = True``. Be sure
|
||||||
|
@ -346,7 +378,7 @@ class ElementBase(object):
|
||||||
#: parent stanza will be passed to the plugin directly.
|
#: parent stanza will be passed to the plugin directly.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 1.0-Beta5
|
#: .. versionadded:: 1.0-Beta5
|
||||||
is_extension = False
|
is_extension: ClassVar[bool] = False
|
||||||
|
|
||||||
#: A map of interface operations to the overriding functions.
|
#: A map of interface operations to the overriding functions.
|
||||||
#: For example, after overriding the ``set`` operation for
|
#: For example, after overriding the ``set`` operation for
|
||||||
|
@ -355,15 +387,15 @@ class ElementBase(object):
|
||||||
#: {'set_body': <some function>}
|
#: {'set_body': <some function>}
|
||||||
#:
|
#:
|
||||||
#: .. versionadded: 1.0-Beta5
|
#: .. versionadded: 1.0-Beta5
|
||||||
plugin_overrides = {}
|
plugin_overrides: ClassVar[Dict[str, str]] = {}
|
||||||
|
|
||||||
#: A mapping of the :attr:`plugin_attrib` values of registered
|
#: A mapping of the :attr:`plugin_attrib` values of registered
|
||||||
#: plugins to their respective classes.
|
#: plugins to their respective classes.
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map: ClassVar[Dict[str, Type[ElementBase]]] = {}
|
||||||
|
|
||||||
#: A mapping of root element tag names (in ``'{namespace}elementname'``
|
#: A mapping of root element tag names (in ``'{namespace}elementname'``
|
||||||
#: format) to the plugin classes responsible for them.
|
#: format) to the plugin classes responsible for them.
|
||||||
plugin_tag_map = {}
|
plugin_tag_map: ClassVar[Dict[str, Type[ElementBase]]] = {}
|
||||||
|
|
||||||
#: The set of stanza classes that can be iterated over using
|
#: The set of stanza classes that can be iterated over using
|
||||||
#: the 'substanzas' interface. Classes are added to this set
|
#: the 'substanzas' interface. Classes are added to this set
|
||||||
|
@ -372,17 +404,26 @@ class ElementBase(object):
|
||||||
#: register_stanza_plugin(DiscoInfo, DiscoItem, iterable=True)
|
#: register_stanza_plugin(DiscoInfo, DiscoItem, iterable=True)
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 1.0-Beta5
|
#: .. versionadded:: 1.0-Beta5
|
||||||
plugin_iterables = set()
|
plugin_iterables: ClassVar[Set[Type[ElementBase]]] = set()
|
||||||
|
|
||||||
#: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
|
#: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
|
||||||
xml_ns = XML_NS
|
xml_ns: ClassVar[str] = XML_NS
|
||||||
|
|
||||||
def __init__(self, xml=None, parent=None):
|
plugins: Dict[Tuple[str, Optional[str]], ElementBase]
|
||||||
|
#: The underlying XML object for the stanza. It is a standard
|
||||||
|
#: :class:`xml.etree.ElementTree` object.
|
||||||
|
xml: ET.Element
|
||||||
|
_index: int
|
||||||
|
loaded_plugins: Set[str]
|
||||||
|
iterables: List[ElementBase]
|
||||||
|
tag: str
|
||||||
|
parent: Optional[ReferenceType[ElementBase]]
|
||||||
|
|
||||||
|
def __init__(self, xml: Optional[ET.Element] = None, parent: Union[Optional[ElementBase], ReferenceType[ElementBase]] = None):
|
||||||
self._index = 0
|
self._index = 0
|
||||||
|
|
||||||
#: The underlying XML object for the stanza. It is a standard
|
if xml is not None:
|
||||||
#: :class:`xml.etree.ElementTree` object.
|
self.xml = xml
|
||||||
self.xml = xml
|
|
||||||
|
|
||||||
#: An ordered dictionary of plugin stanzas, mapped by their
|
#: An ordered dictionary of plugin stanzas, mapped by their
|
||||||
#: :attr:`plugin_attrib` value.
|
#: :attr:`plugin_attrib` value.
|
||||||
|
@ -419,7 +460,7 @@ class ElementBase(object):
|
||||||
existing_xml=child,
|
existing_xml=child,
|
||||||
reuse=False)
|
reuse=False)
|
||||||
|
|
||||||
def setup(self, xml=None):
|
def setup(self, xml: Optional[ET.Element] = None) -> bool:
|
||||||
"""Initialize the stanza's XML contents.
|
"""Initialize the stanza's XML contents.
|
||||||
|
|
||||||
Will return ``True`` if XML was generated according to the stanza's
|
Will return ``True`` if XML was generated according to the stanza's
|
||||||
|
@ -429,29 +470,31 @@ class ElementBase(object):
|
||||||
:param xml: An existing XML object to use for the stanza's content
|
:param xml: An existing XML object to use for the stanza's content
|
||||||
instead of generating new XML.
|
instead of generating new XML.
|
||||||
"""
|
"""
|
||||||
if self.xml is None:
|
if hasattr(self, 'xml'):
|
||||||
|
return False
|
||||||
|
if not hasattr(self, 'xml') and xml is not None:
|
||||||
self.xml = xml
|
self.xml = xml
|
||||||
|
|
||||||
last_xml = self.xml
|
|
||||||
if self.xml is None:
|
|
||||||
# Generate XML from the stanza definition
|
|
||||||
for ename in self.name.split('/'):
|
|
||||||
new = ET.Element("{%s}%s" % (self.namespace, ename))
|
|
||||||
if self.xml is None:
|
|
||||||
self.xml = new
|
|
||||||
else:
|
|
||||||
last_xml.append(new)
|
|
||||||
last_xml = new
|
|
||||||
if self.parent is not None:
|
|
||||||
self.parent().xml.append(self.xml)
|
|
||||||
|
|
||||||
# We had to generate XML
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
# We did not generate XML
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def enable(self, attrib, lang=None):
|
|
||||||
|
# Generate XML from the stanza definition
|
||||||
|
last_xml = ET.Element('')
|
||||||
|
for ename in self.name.split('/'):
|
||||||
|
new = ET.Element("{%s}%s" % (self.namespace, ename))
|
||||||
|
if not hasattr(self, 'xml'):
|
||||||
|
self.xml = new
|
||||||
|
else:
|
||||||
|
last_xml.append(new)
|
||||||
|
last_xml = new
|
||||||
|
if self.parent is not None:
|
||||||
|
parent = self.parent()
|
||||||
|
if parent:
|
||||||
|
parent.xml.append(self.xml)
|
||||||
|
|
||||||
|
# We had to generate XML
|
||||||
|
return True
|
||||||
|
|
||||||
|
def enable(self, attrib: str, lang: Optional[str] = None) -> ElementBase:
|
||||||
"""Enable and initialize a stanza plugin.
|
"""Enable and initialize a stanza plugin.
|
||||||
|
|
||||||
Alias for :meth:`init_plugin`.
|
Alias for :meth:`init_plugin`.
|
||||||
|
@ -487,7 +530,10 @@ class ElementBase(object):
|
||||||
else:
|
else:
|
||||||
return None if check else self.init_plugin(name, lang)
|
return None if check else self.init_plugin(name, lang)
|
||||||
|
|
||||||
def init_plugin(self, attrib, lang=None, existing_xml=None, element=None, reuse=True):
|
def init_plugin(self, attrib: str, lang: Optional[str] = None,
|
||||||
|
existing_xml: Optional[ET.Element] = None,
|
||||||
|
reuse: bool = True,
|
||||||
|
element: Optional[ElementBase] = None) -> ElementBase:
|
||||||
"""Enable and initialize a stanza plugin.
|
"""Enable and initialize a stanza plugin.
|
||||||
|
|
||||||
:param string attrib: The :attr:`plugin_attrib` value of the
|
:param string attrib: The :attr:`plugin_attrib` value of the
|
||||||
|
@ -525,7 +571,7 @@ class ElementBase(object):
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
|
|
||||||
def _get_stanza_values(self):
|
def _get_stanza_values(self) -> Dict[str, Any]:
|
||||||
"""Return A JSON/dictionary version of the XML content
|
"""Return A JSON/dictionary version of the XML content
|
||||||
exposed through the stanza's interfaces::
|
exposed through the stanza's interfaces::
|
||||||
|
|
||||||
|
@ -567,7 +613,7 @@ class ElementBase(object):
|
||||||
values['substanzas'] = iterables
|
values['substanzas'] = iterables
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def _set_stanza_values(self, values):
|
def _set_stanza_values(self, values: Dict[str, Any]) -> ElementBase:
|
||||||
"""Set multiple stanza interface values using a dictionary.
|
"""Set multiple stanza interface values using a dictionary.
|
||||||
|
|
||||||
Stanza plugin values may be set using nested dictionaries.
|
Stanza plugin values may be set using nested dictionaries.
|
||||||
|
@ -623,7 +669,7 @@ class ElementBase(object):
|
||||||
plugin.values = value
|
plugin.values = value
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __getitem__(self, full_attrib):
|
def __getitem__(self, full_attrib: str) -> Any:
|
||||||
"""Return the value of a stanza interface using dict-like syntax.
|
"""Return the value of a stanza interface using dict-like syntax.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
@ -688,7 +734,7 @@ class ElementBase(object):
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def __setitem__(self, attrib, value):
|
def __setitem__(self, attrib: str, value: Any) -> Any:
|
||||||
"""Set the value of a stanza interface using dictionary-like syntax.
|
"""Set the value of a stanza interface using dictionary-like syntax.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
@ -773,7 +819,7 @@ class ElementBase(object):
|
||||||
plugin[full_attrib] = value
|
plugin[full_attrib] = value
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __delitem__(self, attrib):
|
def __delitem__(self, attrib: str) -> Any:
|
||||||
"""Delete the value of a stanza interface using dict-like syntax.
|
"""Delete the value of a stanza interface using dict-like syntax.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
@ -851,7 +897,7 @@ class ElementBase(object):
|
||||||
pass
|
pass
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _set_attr(self, name, value):
|
def _set_attr(self, name: str, value: Optional[JidStr]) -> None:
|
||||||
"""Set the value of a top level attribute of the XML object.
|
"""Set the value of a top level attribute of the XML object.
|
||||||
|
|
||||||
If the new value is None or an empty string, then the attribute will
|
If the new value is None or an empty string, then the attribute will
|
||||||
|
@ -868,7 +914,7 @@ class ElementBase(object):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
self.xml.attrib[name] = value
|
self.xml.attrib[name] = value
|
||||||
|
|
||||||
def _del_attr(self, name):
|
def _del_attr(self, name: str) -> None:
|
||||||
"""Remove a top level attribute of the XML object.
|
"""Remove a top level attribute of the XML object.
|
||||||
|
|
||||||
:param name: The name of the attribute.
|
:param name: The name of the attribute.
|
||||||
|
@ -876,7 +922,7 @@ class ElementBase(object):
|
||||||
if name in self.xml.attrib:
|
if name in self.xml.attrib:
|
||||||
del self.xml.attrib[name]
|
del self.xml.attrib[name]
|
||||||
|
|
||||||
def _get_attr(self, name, default=''):
|
def _get_attr(self, name: str, default: str = '') -> str:
|
||||||
"""Return the value of a top level attribute of the XML object.
|
"""Return the value of a top level attribute of the XML object.
|
||||||
|
|
||||||
In case the attribute has not been set, a default value can be
|
In case the attribute has not been set, a default value can be
|
||||||
|
@ -889,7 +935,8 @@ class ElementBase(object):
|
||||||
"""
|
"""
|
||||||
return self.xml.attrib.get(name, default)
|
return self.xml.attrib.get(name, default)
|
||||||
|
|
||||||
def _get_sub_text(self, name, default='', lang=None):
|
def _get_sub_text(self, name: str, default: str = '',
|
||||||
|
lang: Optional[str] = None) -> Union[str, Dict[str, str]]:
|
||||||
"""Return the text contents of a sub element.
|
"""Return the text contents of a sub element.
|
||||||
|
|
||||||
In case the element does not exist, or it has no textual content,
|
In case the element does not exist, or it has no textual content,
|
||||||
|
@ -900,7 +947,7 @@ class ElementBase(object):
|
||||||
:param default: Optional default to return if the element does
|
:param default: Optional default to return if the element does
|
||||||
not exists. An empty string is returned otherwise.
|
not exists. An empty string is returned otherwise.
|
||||||
"""
|
"""
|
||||||
name = self._fix_ns(name)
|
name = cast(str, self._fix_ns(name))
|
||||||
if lang == '*':
|
if lang == '*':
|
||||||
return self._get_all_sub_text(name, default, None)
|
return self._get_all_sub_text(name, default, None)
|
||||||
|
|
||||||
|
@ -924,8 +971,9 @@ class ElementBase(object):
|
||||||
return result
|
return result
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def _get_all_sub_text(self, name, default='', lang=None):
|
def _get_all_sub_text(self, name: str, default: str = '',
|
||||||
name = self._fix_ns(name)
|
lang: Optional[str] = None) -> Dict[str, str]:
|
||||||
|
name = cast(str, self._fix_ns(name))
|
||||||
|
|
||||||
default_lang = self.get_lang()
|
default_lang = self.get_lang()
|
||||||
results = {}
|
results = {}
|
||||||
|
@ -935,10 +983,16 @@ class ElementBase(object):
|
||||||
stanza_lang = stanza.attrib.get('{%s}lang' % XML_NS,
|
stanza_lang = stanza.attrib.get('{%s}lang' % XML_NS,
|
||||||
default_lang)
|
default_lang)
|
||||||
if not lang or lang == '*' or stanza_lang == lang:
|
if not lang or lang == '*' or stanza_lang == lang:
|
||||||
results[stanza_lang] = stanza.text
|
if stanza.text is None:
|
||||||
|
text = default
|
||||||
|
else:
|
||||||
|
text = stanza.text
|
||||||
|
results[stanza_lang] = text
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def _set_sub_text(self, name, text=None, keep=False, lang=None):
|
def _set_sub_text(self, name: str, text: Optional[str] = None,
|
||||||
|
keep: bool = False,
|
||||||
|
lang: Optional[str] = None) -> Optional[ET.Element]:
|
||||||
"""Set the text contents of a sub element.
|
"""Set the text contents of a sub element.
|
||||||
|
|
||||||
In case the element does not exist, a element will be created,
|
In case the element does not exist, a element will be created,
|
||||||
|
@ -959,15 +1013,16 @@ class ElementBase(object):
|
||||||
lang = default_lang
|
lang = default_lang
|
||||||
|
|
||||||
if not text and not keep:
|
if not text and not keep:
|
||||||
return self._del_sub(name, lang=lang)
|
self._del_sub(name, lang=lang)
|
||||||
|
return None
|
||||||
|
|
||||||
path = self._fix_ns(name, split=True)
|
path = cast(List[str], self._fix_ns(name, split=True))
|
||||||
name = path[-1]
|
name = path[-1]
|
||||||
parent = self.xml
|
parent: Optional[ET.Element] = self.xml
|
||||||
|
|
||||||
# The first goal is to find the parent of the subelement, or, if
|
# The first goal is to find the parent of the subelement, or, if
|
||||||
# we can't find that, the closest grandparent element.
|
# we can't find that, the closest grandparent element.
|
||||||
missing_path = []
|
missing_path: List[str] = []
|
||||||
search_order = path[:-1]
|
search_order = path[:-1]
|
||||||
while search_order:
|
while search_order:
|
||||||
parent = self.xml.find('/'.join(search_order))
|
parent = self.xml.find('/'.join(search_order))
|
||||||
|
@ -1008,15 +1063,17 @@ class ElementBase(object):
|
||||||
parent.append(element)
|
parent.append(element)
|
||||||
return element
|
return element
|
||||||
|
|
||||||
def _set_all_sub_text(self, name, values, keep=False, lang=None):
|
def _set_all_sub_text(self, name: str, values: Dict[str, str],
|
||||||
self._del_sub(name, lang)
|
keep: bool = False,
|
||||||
|
lang: Optional[str] = None) -> None:
|
||||||
|
self._del_sub(name, lang=lang)
|
||||||
for value_lang, value in values.items():
|
for value_lang, value in values.items():
|
||||||
if not lang or lang == '*' or value_lang == lang:
|
if not lang or lang == '*' or value_lang == lang:
|
||||||
self._set_sub_text(name, text=value,
|
self._set_sub_text(name, text=value,
|
||||||
keep=keep,
|
keep=keep,
|
||||||
lang=value_lang)
|
lang=value_lang)
|
||||||
|
|
||||||
def _del_sub(self, name, all=False, lang=None):
|
def _del_sub(self, name: str, all: bool = False, lang: Optional[str] = None) -> None:
|
||||||
"""Remove sub elements that match the given name or XPath.
|
"""Remove sub elements that match the given name or XPath.
|
||||||
|
|
||||||
If the element is in a path, then any parent elements that become
|
If the element is in a path, then any parent elements that become
|
||||||
|
@ -1034,11 +1091,11 @@ class ElementBase(object):
|
||||||
if not lang:
|
if not lang:
|
||||||
lang = default_lang
|
lang = default_lang
|
||||||
|
|
||||||
parent = self.xml
|
parent: Optional[ET.Element] = self.xml
|
||||||
for level, _ in enumerate(path):
|
for level, _ in enumerate(path):
|
||||||
# Generate the paths to the target elements and their parent.
|
# Generate the paths to the target elements and their parent.
|
||||||
element_path = "/".join(path[:len(path) - level])
|
element_path = "/".join(path[:len(path) - level])
|
||||||
parent_path = "/".join(path[:len(path) - level - 1])
|
parent_path: Optional[str] = "/".join(path[:len(path) - level - 1])
|
||||||
|
|
||||||
elements = self.xml.findall(element_path)
|
elements = self.xml.findall(element_path)
|
||||||
if parent_path == '':
|
if parent_path == '':
|
||||||
|
@ -1061,7 +1118,7 @@ class ElementBase(object):
|
||||||
# after deleting the first level of elements.
|
# after deleting the first level of elements.
|
||||||
return
|
return
|
||||||
|
|
||||||
def match(self, xpath):
|
def match(self, xpath: Union[str, List[str]]) -> bool:
|
||||||
"""Compare a stanza object with an XPath-like expression.
|
"""Compare a stanza object with an XPath-like expression.
|
||||||
|
|
||||||
If the XPath matches the contents of the stanza object, the match
|
If the XPath matches the contents of the stanza object, the match
|
||||||
|
@ -1127,7 +1184,7 @@ class ElementBase(object):
|
||||||
# Everything matched.
|
# Everything matched.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key: str, default: Optional[Any] = None) -> Any:
|
||||||
"""Return the value of a stanza interface.
|
"""Return the value of a stanza interface.
|
||||||
|
|
||||||
If the found value is None or an empty string, return the supplied
|
If the found value is None or an empty string, return the supplied
|
||||||
|
@ -1144,7 +1201,7 @@ class ElementBase(object):
|
||||||
return default
|
return default
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def keys(self):
|
def keys(self) -> List[str]:
|
||||||
"""Return the names of all stanza interfaces provided by the
|
"""Return the names of all stanza interfaces provided by the
|
||||||
stanza object.
|
stanza object.
|
||||||
|
|
||||||
|
@ -1158,7 +1215,7 @@ class ElementBase(object):
|
||||||
out.append('substanzas')
|
out.append('substanzas')
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def append(self, item):
|
def append(self, item: Union[ET.Element, ElementBase]) -> ElementBase:
|
||||||
"""Append either an XML object or a substanza to this stanza object.
|
"""Append either an XML object or a substanza to this stanza object.
|
||||||
|
|
||||||
If a substanza object is appended, it will be added to the list
|
If a substanza object is appended, it will be added to the list
|
||||||
|
@ -1189,7 +1246,7 @@ class ElementBase(object):
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def appendxml(self, xml):
|
def appendxml(self, xml: ET.Element) -> ElementBase:
|
||||||
"""Append an XML object to the stanza's XML.
|
"""Append an XML object to the stanza's XML.
|
||||||
|
|
||||||
The added XML will not be included in the list of
|
The added XML will not be included in the list of
|
||||||
|
@ -1200,7 +1257,7 @@ class ElementBase(object):
|
||||||
self.xml.append(xml)
|
self.xml.append(xml)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def pop(self, index=0):
|
def pop(self, index: int = 0) -> ElementBase:
|
||||||
"""Remove and return the last substanza in the list of
|
"""Remove and return the last substanza in the list of
|
||||||
iterable substanzas.
|
iterable substanzas.
|
||||||
|
|
||||||
|
@ -1212,11 +1269,11 @@ class ElementBase(object):
|
||||||
self.xml.remove(substanza.xml)
|
self.xml.remove(substanza.xml)
|
||||||
return substanza
|
return substanza
|
||||||
|
|
||||||
def next(self):
|
def next(self) -> ElementBase:
|
||||||
"""Return the next iterable substanza."""
|
"""Return the next iterable substanza."""
|
||||||
return self.__next__()
|
return self.__next__()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self) -> ElementBase:
|
||||||
"""Remove all XML element contents and plugins.
|
"""Remove all XML element contents and plugins.
|
||||||
|
|
||||||
Any attribute values will be preserved.
|
Any attribute values will be preserved.
|
||||||
|
@ -1229,7 +1286,7 @@ class ElementBase(object):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tag_name(cls):
|
def tag_name(cls) -> str:
|
||||||
"""Return the namespaced name of the stanza's root element.
|
"""Return the namespaced name of the stanza's root element.
|
||||||
|
|
||||||
The format for the tag name is::
|
The format for the tag name is::
|
||||||
|
@ -1241,29 +1298,32 @@ class ElementBase(object):
|
||||||
"""
|
"""
|
||||||
return "{%s}%s" % (cls.namespace, cls.name)
|
return "{%s}%s" % (cls.namespace, cls.name)
|
||||||
|
|
||||||
def get_lang(self, lang=None):
|
def get_lang(self, lang: Optional[str] = None) -> str:
|
||||||
result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
|
result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
|
||||||
if not result and self.parent and self.parent():
|
if not result and self.parent:
|
||||||
return self.parent()['lang']
|
parent = self.parent()
|
||||||
|
if parent:
|
||||||
|
return cast(str, parent['lang'])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def set_lang(self, lang):
|
def set_lang(self, lang: Optional[str]) -> None:
|
||||||
self.del_lang()
|
self.del_lang()
|
||||||
attr = '{%s}lang' % XML_NS
|
attr = '{%s}lang' % XML_NS
|
||||||
if lang:
|
if lang:
|
||||||
self.xml.attrib[attr] = lang
|
self.xml.attrib[attr] = lang
|
||||||
|
|
||||||
def del_lang(self):
|
def del_lang(self) -> None:
|
||||||
attr = '{%s}lang' % XML_NS
|
attr = '{%s}lang' % XML_NS
|
||||||
if attr in self.xml.attrib:
|
if attr in self.xml.attrib:
|
||||||
del self.xml.attrib[attr]
|
del self.xml.attrib[attr]
|
||||||
|
|
||||||
def _fix_ns(self, xpath, split=False, propagate_ns=True):
|
def _fix_ns(self, xpath: str, split: bool = False,
|
||||||
|
propagate_ns: bool = True) -> Union[str, List[str]]:
|
||||||
return fix_ns(xpath, split=split,
|
return fix_ns(xpath, split=split,
|
||||||
propagate_ns=propagate_ns,
|
propagate_ns=propagate_ns,
|
||||||
default_ns=self.namespace)
|
default_ns=self.namespace)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
"""Compare the stanza object with another to test for equality.
|
"""Compare the stanza object with another to test for equality.
|
||||||
|
|
||||||
Stanzas are equal if their interfaces return the same values,
|
Stanzas are equal if their interfaces return the same values,
|
||||||
|
@ -1290,7 +1350,7 @@ class ElementBase(object):
|
||||||
# must be equal.
|
# must be equal.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other: Any) -> bool:
|
||||||
"""Compare the stanza object with another to test for inequality.
|
"""Compare the stanza object with another to test for inequality.
|
||||||
|
|
||||||
Stanzas are not equal if their interfaces return different values,
|
Stanzas are not equal if their interfaces return different values,
|
||||||
|
@ -1300,16 +1360,16 @@ class ElementBase(object):
|
||||||
"""
|
"""
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self) -> bool:
|
||||||
"""Stanza objects should be treated as True in boolean contexts.
|
"""Stanza objects should be treated as True in boolean contexts.
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
"""Return the number of iterable substanzas in this stanza."""
|
"""Return the number of iterable substanzas in this stanza."""
|
||||||
return len(self.iterables)
|
return len(self.iterables)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> ElementBase:
|
||||||
"""Return an iterator object for the stanza's substanzas.
|
"""Return an iterator object for the stanza's substanzas.
|
||||||
|
|
||||||
The iterator is the stanza object itself. Attempting to use two
|
The iterator is the stanza object itself. Attempting to use two
|
||||||
|
@ -1318,7 +1378,7 @@ class ElementBase(object):
|
||||||
self._index = 0
|
self._index = 0
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self) -> ElementBase:
|
||||||
"""Return the next iterable substanza."""
|
"""Return the next iterable substanza."""
|
||||||
self._index += 1
|
self._index += 1
|
||||||
if self._index > len(self.iterables):
|
if self._index > len(self.iterables):
|
||||||
|
@ -1326,13 +1386,16 @@ class ElementBase(object):
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
return self.iterables[self._index - 1]
|
return self.iterables[self._index - 1]
|
||||||
|
|
||||||
def __copy__(self):
|
def __copy__(self) -> ElementBase:
|
||||||
"""Return a copy of the stanza object that does not share the same
|
"""Return a copy of the stanza object that does not share the same
|
||||||
underlying XML object.
|
underlying XML object.
|
||||||
"""
|
"""
|
||||||
return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent)
|
return self.__class__(
|
||||||
|
xml=copy.deepcopy(self.xml),
|
||||||
|
parent=self.parent,
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self, top_level_ns=True):
|
def __str__(self, top_level_ns: bool = True) -> str:
|
||||||
"""Return a string serialization of the underlying XML object.
|
"""Return a string serialization of the underlying XML object.
|
||||||
|
|
||||||
.. seealso:: :ref:`tostring`
|
.. seealso:: :ref:`tostring`
|
||||||
|
@ -1343,12 +1406,33 @@ class ElementBase(object):
|
||||||
return tostring(self.xml, xmlns='',
|
return tostring(self.xml, xmlns='',
|
||||||
top_level=True)
|
top_level=True)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
"""Use the stanza's serialized XML as its representation."""
|
"""Use the stanza's serialized XML as its representation."""
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
# Compatibility.
|
# Compatibility.
|
||||||
_get_plugin = get_plugin
|
_get_plugin = get_plugin
|
||||||
|
get_stanza_values = _get_stanza_values
|
||||||
|
set_stanza_values = _set_stanza_values
|
||||||
|
|
||||||
|
#: A JSON/dictionary version of the XML content exposed through
|
||||||
|
#: the stanza interfaces::
|
||||||
|
#:
|
||||||
|
#: >>> msg = Message()
|
||||||
|
#: >>> msg.values
|
||||||
|
#: {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
|
||||||
|
#: 'to': , 'type': 'normal', 'id': '', 'subject': ''}
|
||||||
|
#:
|
||||||
|
#: Likewise, assigning to the :attr:`values` will change the XML
|
||||||
|
#: content::
|
||||||
|
#:
|
||||||
|
#: >>> msg = Message()
|
||||||
|
#: >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
|
||||||
|
#: >>> msg
|
||||||
|
#: '<message to="user@example.com"><body>Hi!</body></message>'
|
||||||
|
#:
|
||||||
|
#: Child stanzas are exposed as nested dictionaries.
|
||||||
|
values = property(_get_stanza_values, _set_stanza_values) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class StanzaBase(ElementBase):
|
class StanzaBase(ElementBase):
|
||||||
|
@ -1386,9 +1470,14 @@ class StanzaBase(ElementBase):
|
||||||
|
|
||||||
#: The default XMPP client namespace
|
#: The default XMPP client namespace
|
||||||
namespace = 'jabber:client'
|
namespace = 'jabber:client'
|
||||||
|
types: ClassVar[Set[str]] = set()
|
||||||
|
|
||||||
def __init__(self, stream=None, xml=None, stype=None,
|
def __init__(self, stream: Optional[XMLStream] = None,
|
||||||
sto=None, sfrom=None, sid=None, parent=None, recv=False):
|
xml: Optional[ET.Element] = None,
|
||||||
|
stype: Optional[str] = None,
|
||||||
|
sto: Optional[JidStr] = None, sfrom: Optional[JidStr] = None,
|
||||||
|
sid: Optional[str] = None,
|
||||||
|
parent: Optional[ElementBase] = None, recv: bool = False):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
if stream is not None:
|
if stream is not None:
|
||||||
self.namespace = stream.default_ns
|
self.namespace = stream.default_ns
|
||||||
|
@ -1403,7 +1492,7 @@ class StanzaBase(ElementBase):
|
||||||
self['id'] = sid
|
self['id'] = sid
|
||||||
self.tag = "{%s}%s" % (self.namespace, self.name)
|
self.tag = "{%s}%s" % (self.namespace, self.name)
|
||||||
|
|
||||||
def set_type(self, value):
|
def set_type(self, value: str) -> StanzaBase:
|
||||||
"""Set the stanza's ``'type'`` attribute.
|
"""Set the stanza's ``'type'`` attribute.
|
||||||
|
|
||||||
Only type values contained in :attr:`types` are accepted.
|
Only type values contained in :attr:`types` are accepted.
|
||||||
|
@ -1414,11 +1503,11 @@ class StanzaBase(ElementBase):
|
||||||
self.xml.attrib['type'] = value
|
self.xml.attrib['type'] = value
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_to(self):
|
def get_to(self) -> JID:
|
||||||
"""Return the value of the stanza's ``'to'`` attribute."""
|
"""Return the value of the stanza's ``'to'`` attribute."""
|
||||||
return JID(self._get_attr('to'))
|
return JID(self._get_attr('to'))
|
||||||
|
|
||||||
def set_to(self, value):
|
def set_to(self, value: JidStr) -> None:
|
||||||
"""Set the ``'to'`` attribute of the stanza.
|
"""Set the ``'to'`` attribute of the stanza.
|
||||||
|
|
||||||
:param value: A string or :class:`slixmpp.xmlstream.JID` object
|
:param value: A string or :class:`slixmpp.xmlstream.JID` object
|
||||||
|
@ -1426,11 +1515,11 @@ class StanzaBase(ElementBase):
|
||||||
"""
|
"""
|
||||||
return self._set_attr('to', str(value))
|
return self._set_attr('to', str(value))
|
||||||
|
|
||||||
def get_from(self):
|
def get_from(self) -> JID:
|
||||||
"""Return the value of the stanza's ``'from'`` attribute."""
|
"""Return the value of the stanza's ``'from'`` attribute."""
|
||||||
return JID(self._get_attr('from'))
|
return JID(self._get_attr('from'))
|
||||||
|
|
||||||
def set_from(self, value):
|
def set_from(self, value: JidStr) -> None:
|
||||||
"""Set the 'from' attribute of the stanza.
|
"""Set the 'from' attribute of the stanza.
|
||||||
|
|
||||||
:param from: A string or JID object representing the sender's JID.
|
:param from: A string or JID object representing the sender's JID.
|
||||||
|
@ -1438,11 +1527,11 @@ class StanzaBase(ElementBase):
|
||||||
"""
|
"""
|
||||||
return self._set_attr('from', str(value))
|
return self._set_attr('from', str(value))
|
||||||
|
|
||||||
def get_payload(self):
|
def get_payload(self) -> List[ET.Element]:
|
||||||
"""Return a list of XML objects contained in the stanza."""
|
"""Return a list of XML objects contained in the stanza."""
|
||||||
return list(self.xml)
|
return list(self.xml)
|
||||||
|
|
||||||
def set_payload(self, value):
|
def set_payload(self, value: Union[List[ElementBase], ElementBase]) -> StanzaBase:
|
||||||
"""Add XML content to the stanza.
|
"""Add XML content to the stanza.
|
||||||
|
|
||||||
:param value: Either an XML or a stanza object, or a list
|
:param value: Either an XML or a stanza object, or a list
|
||||||
|
@ -1454,12 +1543,12 @@ class StanzaBase(ElementBase):
|
||||||
self.append(val)
|
self.append(val)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def del_payload(self):
|
def del_payload(self) -> StanzaBase:
|
||||||
"""Remove the XML contents of the stanza."""
|
"""Remove the XML contents of the stanza."""
|
||||||
self.clear()
|
self.clear()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def reply(self, clear=True):
|
def reply(self, clear: bool = True) -> StanzaBase:
|
||||||
"""Prepare the stanza for sending a reply.
|
"""Prepare the stanza for sending a reply.
|
||||||
|
|
||||||
Swaps the ``'from'`` and ``'to'`` attributes.
|
Swaps the ``'from'`` and ``'to'`` attributes.
|
||||||
|
@ -1475,7 +1564,7 @@ class StanzaBase(ElementBase):
|
||||||
new_stanza = copy.copy(self)
|
new_stanza = copy.copy(self)
|
||||||
# if it's a component, use from
|
# if it's a component, use from
|
||||||
if self.stream and hasattr(self.stream, "is_component") and \
|
if self.stream and hasattr(self.stream, "is_component") and \
|
||||||
self.stream.is_component:
|
getattr(self.stream, 'is_component'):
|
||||||
new_stanza['from'], new_stanza['to'] = self['to'], self['from']
|
new_stanza['from'], new_stanza['to'] = self['to'], self['from']
|
||||||
else:
|
else:
|
||||||
new_stanza['to'] = self['from']
|
new_stanza['to'] = self['from']
|
||||||
|
@ -1484,19 +1573,19 @@ class StanzaBase(ElementBase):
|
||||||
new_stanza.clear()
|
new_stanza.clear()
|
||||||
return new_stanza
|
return new_stanza
|
||||||
|
|
||||||
def error(self):
|
def error(self) -> StanzaBase:
|
||||||
"""Set the stanza's type to ``'error'``."""
|
"""Set the stanza's type to ``'error'``."""
|
||||||
self['type'] = 'error'
|
self['type'] = 'error'
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def unhandled(self):
|
def unhandled(self) -> None:
|
||||||
"""Called if no handlers have been registered to process this stanza.
|
"""Called if no handlers have been registered to process this stanza.
|
||||||
|
|
||||||
Meant to be overridden.
|
Meant to be overridden.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def exception(self, e):
|
def exception(self, e: Exception) -> None:
|
||||||
"""Handle exceptions raised during stanza processing.
|
"""Handle exceptions raised during stanza processing.
|
||||||
|
|
||||||
Meant to be overridden.
|
Meant to be overridden.
|
||||||
|
@ -1504,18 +1593,21 @@ class StanzaBase(ElementBase):
|
||||||
log.exception('Error handling {%s}%s stanza', self.namespace,
|
log.exception('Error handling {%s}%s stanza', self.namespace,
|
||||||
self.name)
|
self.name)
|
||||||
|
|
||||||
def send(self):
|
def send(self) -> None:
|
||||||
"""Queue the stanza to be sent on the XML stream."""
|
"""Queue the stanza to be sent on the XML stream."""
|
||||||
self.stream.send(self)
|
if self.stream is not None:
|
||||||
|
self.stream.send(self)
|
||||||
|
else:
|
||||||
|
log.error("Tried to send stanza without a stream: %s", self)
|
||||||
|
|
||||||
def __copy__(self):
|
def __copy__(self) -> StanzaBase:
|
||||||
"""Return a copy of the stanza object that does not share the
|
"""Return a copy of the stanza object that does not share the
|
||||||
same underlying XML object, but does share the same XML stream.
|
same underlying XML object, but does share the same XML stream.
|
||||||
"""
|
"""
|
||||||
return self.__class__(xml=copy.deepcopy(self.xml),
|
return self.__class__(xml=copy.deepcopy(self.xml),
|
||||||
stream=self.stream)
|
stream=self.stream)
|
||||||
|
|
||||||
def __str__(self, top_level_ns=False):
|
def __str__(self, top_level_ns: bool = False) -> str:
|
||||||
"""Serialize the stanza's XML to a string.
|
"""Serialize the stanza's XML to a string.
|
||||||
|
|
||||||
:param bool top_level_ns: Display the top-most namespace.
|
:param bool top_level_ns: Display the top-most namespace.
|
||||||
|
@ -1525,27 +1617,3 @@ class StanzaBase(ElementBase):
|
||||||
return tostring(self.xml, xmlns=xmlns,
|
return tostring(self.xml, xmlns=xmlns,
|
||||||
stream=self.stream,
|
stream=self.stream,
|
||||||
top_level=(self.stream is None))
|
top_level=(self.stream is None))
|
||||||
|
|
||||||
|
|
||||||
#: A JSON/dictionary version of the XML content exposed through
|
|
||||||
#: the stanza interfaces::
|
|
||||||
#:
|
|
||||||
#: >>> msg = Message()
|
|
||||||
#: >>> msg.values
|
|
||||||
#: {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
|
|
||||||
#: 'to': , 'type': 'normal', 'id': '', 'subject': ''}
|
|
||||||
#:
|
|
||||||
#: Likewise, assigning to the :attr:`values` will change the XML
|
|
||||||
#: content::
|
|
||||||
#:
|
|
||||||
#: >>> msg = Message()
|
|
||||||
#: >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
|
|
||||||
#: >>> msg
|
|
||||||
#: '<message to="user@example.com"><body>Hi!</body></message>'
|
|
||||||
#:
|
|
||||||
#: Child stanzas are exposed as nested dictionaries.
|
|
||||||
ElementBase.values = property(ElementBase._get_stanza_values,
|
|
||||||
ElementBase._set_stanza_values)
|
|
||||||
|
|
||||||
ElementBase.get_stanza_values = ElementBase._get_stanza_values
|
|
||||||
ElementBase.set_stanza_values = ElementBase._set_stanza_values
|
|
||||||
|
|
Loading…
Reference in a new issue