summaryrefslogtreecommitdiff
path: root/slixmpp/jid.py
diff options
context:
space:
mode:
authorEmmanuel Gil Peyrot <linkmauve@linkmauve.fr>2015-06-12 00:46:47 +0100
committerEmmanuel Gil Peyrot <emmanuel.peyrot@collabora.com>2015-06-20 01:49:48 +0100
commit4afbb0322bb12023631e644671716fb413a32d13 (patch)
tree40471de2f23b76a5a1cf6ec63b50b277d44c4626 /slixmpp/jid.py
parent7bce1ecc8aeeb33bcf25474647aeb86245c71c1c (diff)
downloadslixmpp-4afbb0322bb12023631e644671716fb413a32d13.tar.gz
slixmpp-4afbb0322bb12023631e644671716fb413a32d13.tar.bz2
slixmpp-4afbb0322bb12023631e644671716fb413a32d13.tar.xz
slixmpp-4afbb0322bb12023631e644671716fb413a32d13.zip
Rework slixmpp.jid’s JID classes to make them more efficient.
Diffstat (limited to 'slixmpp/jid.py')
-rw-r--r--slixmpp/jid.py328
1 files changed, 120 insertions, 208 deletions
diff --git a/slixmpp/jid.py b/slixmpp/jid.py
index c03c5df7..abb8067a 100644
--- a/slixmpp/jid.py
+++ b/slixmpp/jid.py
@@ -11,12 +11,11 @@
:license: MIT, see LICENSE for more details
"""
-from __future__ import unicode_literals
-
import re
import socket
from copy import deepcopy
+from functools import lru_cache
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
@@ -30,22 +29,8 @@ JID_PATTERN = re.compile(
)
#: The set of escape sequences for the characters not allowed by nodeprep.
-JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f',
- '\\3a', '\\3c', '\\3e', '\\40', '\\5c'])
-
-#: A mapping of unallowed characters to their escape sequences. An escape
-#: sequence for '\' is also included since it must also be escaped in
-#: certain situations.
-JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20',
- '"': '\\22',
- '&': '\\26',
- "'": '\\27',
- '/': '\\2f',
- ':': '\\3a',
- '<': '\\3c',
- '>': '\\3e',
- '@': '\\40',
- '\\': '\\5c'}
+JID_ESCAPE_SEQUENCES = {'\\20', '\\22', '\\26', '\\27', '\\2f',
+ '\\3a', '\\3c', '\\3e', '\\40', '\\5c'}
#: The reverse mapping of escape sequences to their original forms.
JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ',
@@ -60,6 +45,8 @@ JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ',
'\\5c': '\\'}
+# TODO: Find the best cache size for a standard usage.
+@lru_cache(maxsize=1024)
def _parse_jid(data):
"""
Parse string data into the node, domain, and resource
@@ -91,17 +78,19 @@ def _validate_node(node):
:returns: The local portion of a JID, as validated by nodeprep.
"""
- if node is not None:
- try:
- node = nodeprep(node)
- except StringprepError:
- raise InvalidJID('Nodeprep failed')
+ if node is None:
+ return ''
- if not node:
- raise InvalidJID('Localpart must not be 0 bytes')
- if len(node) > 1023:
- raise InvalidJID('Localpart must be less than 1024 bytes')
- return node
+ try:
+ node = nodeprep(node)
+ except StringprepError:
+ raise InvalidJID('Nodeprep failed')
+
+ if not node:
+ raise InvalidJID('Localpart must not be 0 bytes')
+ if len(node) > 1023:
+ raise InvalidJID('Localpart must be less than 1024 bytes')
+ return node
def _validate_domain(domain):
@@ -170,42 +159,19 @@ def _validate_resource(resource):
:returns: The local portion of a JID, as validated by resourceprep.
"""
- if resource is not None:
- try:
- resource = resourceprep(resource)
- except StringprepError:
- raise InvalidJID('Resourceprep failed')
-
- if not resource:
- raise InvalidJID('Resource must not be 0 bytes')
- if len(resource) > 1023:
- raise InvalidJID('Resource must be less than 1024 bytes')
- return resource
-
-
-def _escape_node(node):
- """Escape the local portion of a JID."""
- result = []
+ if resource is None:
+ return ''
- for i, char in enumerate(node):
- if char == '\\':
- if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES:
- result.append('\\5c')
- continue
- result.append(char)
-
- for i, char in enumerate(result):
- if char != '\\':
- result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char)
-
- escaped = ''.join(result)
-
- if escaped.startswith('\\20') or escaped.endswith('\\20'):
- raise InvalidJID('Escaped local part starts or ends with "\\20"')
-
- _validate_node(escaped)
+ try:
+ resource = resourceprep(resource)
+ except StringprepError:
+ raise InvalidJID('Resourceprep failed')
- return escaped
+ if not resource:
+ raise InvalidJID('Resource must not be 0 bytes')
+ if len(resource) > 1023:
+ raise InvalidJID('Resource must be less than 1024 bytes')
+ return resource
def _unescape_node(node):
@@ -230,9 +196,7 @@ def _unescape_node(node):
seq = seq[1:]
else:
unescaped.append(char)
- unescaped = ''.join(unescaped)
-
- return unescaped
+ return ''.join(unescaped)
def _format_jid(local=None, domain=None, resource=None):
@@ -266,47 +230,47 @@ class InvalidJID(ValueError):
"""
# pylint: disable=R0903
-class UnescapedJID(object):
+class UnescapedJID:
"""
.. versionadded:: 1.1.10
"""
- def __init__(self, local, domain, resource):
- self._jid = (local, domain, resource)
+ __slots__ = ('_node', '_domain', '_resource')
- # pylint: disable=R0911
- def __getattr__(self, name):
+ def __init__(self, node, domain, resource):
+ self._node = node
+ self._domain = domain
+ self._resource = resource
+
+ def __getattribute__(self, name):
"""Retrieve the given JID component.
:param name: one of: user, server, domain, resource,
full, or bare.
"""
if name == 'resource':
- return self._jid[2] or ''
- elif name in ('user', 'username', 'local', 'node'):
- return self._jid[0] or ''
- elif name in ('server', 'domain', 'host'):
- return self._jid[1] or ''
- elif name in ('full', 'jid'):
- return _format_jid(*self._jid)
- elif name == 'bare':
- return _format_jid(self._jid[0], self._jid[1])
- elif name == '_jid':
- return getattr(super(JID, self), '_jid')
- else:
- return None
+ return self._resource
+ if name in ('user', 'username', 'local', 'node'):
+ return self._node
+ if name in ('server', 'domain', 'host'):
+ return self._domain
+ if name in ('full', 'jid'):
+ return _format_jid(self._node, self._domain, self._resource)
+ if name == 'bare':
+ return _format_jid(self._node, self._domain)
+ return object.__getattribute__(self, name)
def __str__(self):
"""Use the full JID as the string value."""
- return _format_jid(*self._jid)
+ return _format_jid(self._node, self._domain, self._resource)
def __repr__(self):
"""Use the full JID as the representation."""
- return self.__str__()
+ return _format_jid(self._node, self._domain, self._resource)
-class JID(object):
+class JID:
"""
A representation of a Jabber ID, or JID.
@@ -318,13 +282,13 @@ class JID(object):
The JID is a full JID otherwise.
**JID Properties:**
- :jid: Alias for ``full``.
:full: The string value of the full JID.
+ :jid: Alias for ``full``.
:bare: The string value of the bare JID.
- :user: The username portion of the JID.
- :username: Alias for ``user``.
- :local: Alias for ``user``.
- :node: Alias for ``user``.
+ :node: The node portion of the JID.
+ :user: Alias for ``node``.
+ :local: Alias for ``node``.
+ :username: Alias for ``node``.
:domain: The domain name portion of the JID.
:server: Alias for ``domain``.
:host: Alias for ``domain``.
@@ -332,49 +296,23 @@ class JID(object):
:param string jid:
A string of the form ``'[user@]domain[/resource]'``.
- :param string local:
- Optional. Specify the local, or username, portion
- of the JID. If provided, it will override the local
- value provided by the `jid` parameter. The given
- local value will also be escaped if necessary.
- :param string domain:
- Optional. Specify the domain of the JID. If
- provided, it will override the domain given by
- the `jid` parameter.
- :param string resource:
- Optional. Specify the resource value of the JID.
- If provided, it will override the domain given
- by the `jid` parameter.
:raises InvalidJID:
"""
- # pylint: disable=W0212
- def __init__(self, jid=None, **kwargs):
- in_local = kwargs.get('local', None)
- in_domain = kwargs.get('domain', None)
- in_resource = kwargs.get('resource', None)
- parts = None
- if in_local or in_domain or in_resource:
- parts = (in_local, in_domain, in_resource)
+ __slots__ = ('_node', '_domain', '_resource')
+ def __init__(self, jid=None):
if not jid:
- parsed_jid = (None, None, None)
+ self._node = ''
+ self._domain = ''
+ self._resource = ''
elif not isinstance(jid, JID):
- parsed_jid = _parse_jid(jid)
+ self._node, self._domain, self._resource = _parse_jid(jid)
else:
- parsed_jid = jid._jid
-
- local, domain, resource = parsed_jid
-
- if 'local' in kwargs:
- local = _escape_node(in_local)
- if 'domain' in kwargs:
- domain = _validate_domain(in_domain)
- if 'resource' in kwargs:
- resource = _validate_resource(in_resource)
-
- self._jid = (local, domain, resource)
+ self._node = jid._node
+ self._domain = jid._domain
+ self._resource = jid._resource
def unescape(self):
"""Return an unescaped JID object.
@@ -387,151 +325,125 @@ class JID(object):
.. versionadded:: 1.1.10
"""
- return UnescapedJID(_unescape_node(self._jid[0]),
- self._jid[1],
- self._jid[2])
-
- def regenerate(self):
- """No-op
-
- .. deprecated:: 1.1.10
- """
- pass
-
- def reset(self, data):
- """Start fresh from a new JID string.
-
- :param string data: A string of the form ``'[user@]domain[/resource]'``.
-
- .. deprecated:: 1.1.10
- """
- self._jid = JID(data)._jid
+ return UnescapedJID(_unescape_node(self._node),
+ self._domain,
+ self._resource)
@property
- def resource(self):
- return self._jid[2] or ''
+ def node(self):
+ return self._node
@property
def user(self):
- return self._jid[0] or ''
+ return self._node
@property
def local(self):
- return self._jid[0] or ''
-
- @property
- def node(self):
- return self._jid[0] or ''
+ return self._node
@property
def username(self):
- return self._jid[0] or ''
+ return self._node
@property
- def bare(self):
- return _format_jid(self._jid[0], self._jid[1])
+ def domain(self):
+ return self._domain
@property
def server(self):
- return self._jid[1] or ''
-
- @property
- def domain(self):
- return self._jid[1] or ''
+ return self._domain
@property
def host(self):
- return self._jid[1] or ''
+ return self._domain
@property
- def full(self):
- return _format_jid(*self._jid)
+ def bare(self):
+ return _format_jid(self._node, self._domain)
@property
- def jid(self):
- return _format_jid(*self._jid)
+ def resource(self):
+ return self._resource
@property
- def bare(self):
- return _format_jid(self._jid[0], self._jid[1])
+ def full(self):
+ return _format_jid(self._node, self._domain, self._resource)
+ @property
+ def jid(self):
+ return _format_jid(self._node, self._domain, self._resource)
- @resource.setter
- def resource(self, value):
- self._jid = JID(self, resource=value)._jid
+ @node.setter
+ def node(self, value):
+ self._node = _validate_node(value)
@user.setter
def user(self, value):
- self._jid = JID(self, local=value)._jid
-
- @username.setter
- def username(self, value):
- self._jid = JID(self, local=value)._jid
+ self._node = _validate_node(value)
@local.setter
def local(self, value):
- self._jid = JID(self, local=value)._jid
-
- @node.setter
- def node(self, value):
- self._jid = JID(self, local=value)._jid
+ self._node = _validate_node(value)
- @server.setter
- def server(self, value):
- self._jid = JID(self, domain=value)._jid
+ @username.setter
+ def username(self, value):
+ self._node = _validate_node(value)
@domain.setter
def domain(self, value):
- self._jid = JID(self, domain=value)._jid
+ self._domain = _validate_domain(value)
+
+ @server.setter
+ def server(self, value):
+ self._domain = _validate_domain(value)
@host.setter
def host(self, value):
- self._jid = JID(self, domain=value)._jid
+ self._domain = _validate_domain(value)
+
+ @bare.setter
+ def bare(self, value):
+ node, domain, resource = _parse_jid(value)
+ assert not resource
+ self._node = node
+ self._domain = domain
+
+ @resource.setter
+ def resource(self, value):
+ self._resource = _validate_resource(value)
@full.setter
def full(self, value):
- self._jid = JID(value)._jid
+ self._node, self._domain, self._resource = _parse_jid(value)
@jid.setter
def jid(self, value):
- self._jid = JID(value)._jid
-
- @bare.setter
- def bare(self, value):
- parsed = JID(value)._jid
- self._jid = (parsed[0], parsed[1], self._jid[2])
-
+ self._node, self._domain, self._resource = _parse_jid(value)
def __str__(self):
"""Use the full JID as the string value."""
- return _format_jid(*self._jid)
+ return _format_jid(self._node, self._domain, self._resource)
def __repr__(self):
"""Use the full JID as the representation."""
- return self.__str__()
+ return _format_jid(self._node, self._domain, self._resource)
# pylint: disable=W0212
def __eq__(self, other):
"""Two JIDs are equal if they have the same full JID value."""
if isinstance(other, UnescapedJID):
return False
+ if not isinstance(other, JID):
+ other = JID(other)
- other = JID(other)
- return self._jid == other._jid
+ return (self._node == other._node and
+ self._domain == other._domain and
+ self._resource == other._resource)
- # pylint: disable=W0212
def __ne__(self, other):
"""Two JIDs are considered unequal if they are not equal."""
return not self == other
def __hash__(self):
"""Hash a JID based on the string version of its full JID."""
- return hash(self.__str__())
-
- def __copy__(self):
- """Generate a duplicate JID."""
- return JID(self)
-
- def __deepcopy__(self, memo):
- """Generate a duplicate JID."""
- return JID(deepcopy(str(self), memo))
+ return hash(_format_jid(self._node, self._domain, self._resource))