Compare commits
31 Commits
slix-1.4.2
...
adhoc-exec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
412a9169bd | ||
|
|
72b355de8c | ||
|
|
af246dcfe1 | ||
|
|
9612e518fb | ||
|
|
fde8264191 | ||
|
|
1cdc656208 | ||
|
|
0042108a67 | ||
|
|
704161a285 | ||
|
|
6b1b58a339 | ||
|
|
4f96e5fa75 | ||
|
|
bcb90a653e | ||
|
|
7e435b703d | ||
|
|
2dda6b80d4 | ||
|
|
5629e44710 | ||
|
|
6a06881d8b | ||
|
|
2b666eb1de | ||
|
|
400e7a3903 | ||
|
|
fbab3ad214 | ||
|
|
628b357b06 | ||
|
|
88260cc240 | ||
|
|
e9f2f503b8 | ||
|
|
696a72247b | ||
|
|
05d76e4b1d | ||
|
|
d52d4fbbbe | ||
|
|
e53c0fcb30 | ||
|
|
97d68c5196 | ||
|
|
b42fafabb4 | ||
|
|
3a44ec8f15 | ||
|
|
93f385562f | ||
|
|
9cab02438b | ||
|
|
74ed50e626 |
@@ -5,7 +5,7 @@ To contribute, the preferred way is to commit your changes on some
|
|||||||
publicly-available git repository (on a fork `on github
|
publicly-available git repository (on a fork `on github
|
||||||
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
|
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
|
||||||
notify the developers with either:
|
notify the developers with either:
|
||||||
- a ticket `on the bug tracker <https://dev.poez.io/new>`_
|
- a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_
|
||||||
- a pull request on github
|
- a pull request on github
|
||||||
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
|
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
|
||||||
|
|
||||||
|
|||||||
25
LICENSE
25
LICENSE
@@ -167,28 +167,3 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
socksipy: A Python SOCKS client module.
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Copyright 2006 Dan-Haim. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
|
||||||
to endorse or promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
||||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
||||||
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
|
||||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
||||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ Slixmpp Credits
|
|||||||
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
|
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
|
||||||
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
|
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
|
||||||
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
|
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
|
||||||
|
- Maxime Buquet (`pep <xmpp:pep@bouah.net?message>`_)
|
||||||
|
|
||||||
Credits (SleekXMPP)
|
Credits (SleekXMPP)
|
||||||
-------------------
|
-------------------
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Create and Run a Server Component
|
|||||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
|
|
||||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||||
with `Git <http://git.poez.io/slixmpp>`_.
|
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
|
||||||
|
|
||||||
Many XMPP applications eventually graduate to requiring to run as a server
|
Many XMPP applications eventually graduate to requiring to run as a server
|
||||||
component in order to meet scalability requirements. To demonstrate how to
|
component in order to meet scalability requirements. To demonstrate how to
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Slixmpp Quickstart - Echo Bot
|
|||||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
|
|
||||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||||
with `Git <http://git.poez.io/slixmpp>`_.
|
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
|
||||||
|
|
||||||
As a basic starting project, we will create an echo bot which will reply to any
|
As a basic starting project, we will create an echo bot which will reply to any
|
||||||
messages sent to it. We will also go through adding some basic command line configuration
|
messages sent to it. We will also go through adding some basic command line configuration
|
||||||
@@ -329,7 +329,7 @@ The Final Product
|
|||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Here then is what the final result should look like after working through the guide above. The code
|
Here then is what the final result should look like after working through the guide above. The code
|
||||||
can also be found in the Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/examples>`_.
|
can also be found in the Slixmpp `examples directory <https://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_.
|
||||||
|
|
||||||
.. compound::
|
.. compound::
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Multi-User Chat (MUC) Bot
|
|||||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
|
|
||||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||||
from `Git <http://git.poez.io/slixmpp>`_.
|
from `Git <https://lab.louiz.org/poezio/slixmpp>`_.
|
||||||
|
|
||||||
Now that you've got the basic gist of using Slixmpp by following the
|
Now that you've got the basic gist of using Slixmpp by following the
|
||||||
echobot example (:ref:`echobot`), we can use one of the bundled plugins
|
echobot example (:ref:`echobot`), we can use one of the bundled plugins
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ Slixmpp
|
|||||||
.. sidebar:: Get the Code
|
.. sidebar:: Get the Code
|
||||||
|
|
||||||
The latest source code for Slixmpp may be found on the `Git repo
|
The latest source code for Slixmpp may be found on the `Git repo
|
||||||
<http://git.poez.io/slixmpp>`_. ::
|
<https://lab.louiz.org/poezio/slixmpp>`_. ::
|
||||||
|
|
||||||
git clone git://git.poez.io/slixmpp
|
git clone https://lab.louiz.org/poezio/slixmpp
|
||||||
|
|
||||||
An XMPP chat room is available for discussing and getting help with slixmpp.
|
An XMPP chat room is available for discussing and getting help with slixmpp.
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ Slixmpp
|
|||||||
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
|
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
|
||||||
|
|
||||||
**Reporting bugs**
|
**Reporting bugs**
|
||||||
You can report bugs at http://dev.louiz.org/projects/slixmpp/issues.
|
You can report bugs at http://lab.louiz.org/poezio/slixmpp/issues.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_
|
slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_
|
||||||
|
|||||||
5
setup.py
5
setup.py
@@ -20,8 +20,7 @@ from run_tests import TestCommand
|
|||||||
from slixmpp.version import __version__
|
from slixmpp.version import __version__
|
||||||
|
|
||||||
VERSION = __version__
|
VERSION = __version__
|
||||||
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, '
|
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber).')
|
||||||
'Google Talk, etc).')
|
|
||||||
with open('README.rst', encoding='utf8') as readme:
|
with open('README.rst', encoding='utf8') as readme:
|
||||||
LONG_DESCRIPTION = readme.read()
|
LONG_DESCRIPTION = readme.read()
|
||||||
|
|
||||||
@@ -79,7 +78,7 @@ setup(
|
|||||||
long_description=LONG_DESCRIPTION,
|
long_description=LONG_DESCRIPTION,
|
||||||
author='Florent Le Coz',
|
author='Florent Le Coz',
|
||||||
author_email='louiz@louiz.org',
|
author_email='louiz@louiz.org',
|
||||||
url='https://dev.louiz.org/projects/slixmpp',
|
url='https://lab.louiz.org/poezio/slixmpp',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
platforms=['any'],
|
platforms=['any'],
|
||||||
packages=packages,
|
packages=packages,
|
||||||
|
|||||||
@@ -104,12 +104,12 @@ class BaseXMPP(XMLStream):
|
|||||||
#: :attr:`use_message_ids` to `True` will assign all outgoing
|
#: :attr:`use_message_ids` to `True` will assign all outgoing
|
||||||
#: messages an ID. Some plugin features require enabling
|
#: messages an ID. Some plugin features require enabling
|
||||||
#: this option.
|
#: this option.
|
||||||
self.use_message_ids = False
|
self.use_message_ids = True
|
||||||
|
|
||||||
#: Presence updates may optionally be tagged with ID values.
|
#: Presence updates may optionally be tagged with ID values.
|
||||||
#: Setting :attr:`use_message_ids` to `True` will assign all
|
#: Setting :attr:`use_message_ids` to `True` will assign all
|
||||||
#: outgoing messages an ID.
|
#: outgoing messages an ID.
|
||||||
self.use_presence_ids = False
|
self.use_presence_ids = True
|
||||||
|
|
||||||
#: The API registry is a way to process callbacks based on
|
#: The API registry is a way to process callbacks based on
|
||||||
#: JID+node combinations. Each callback in the registry is
|
#: JID+node combinations. Each callback in the registry is
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import socket
|
|||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
|
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ def _parse_jid(data):
|
|||||||
return node, domain, resource
|
return node, domain, resource
|
||||||
|
|
||||||
|
|
||||||
def _validate_node(node):
|
def _validate_node(node: Optional[str]):
|
||||||
"""Validate the local, or username, portion of a JID.
|
"""Validate the local, or username, portion of a JID.
|
||||||
|
|
||||||
:raises InvalidJID:
|
:raises InvalidJID:
|
||||||
@@ -93,7 +94,7 @@ def _validate_node(node):
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
def _validate_domain(domain):
|
def _validate_domain(domain: str):
|
||||||
"""Validate the domain portion of a JID.
|
"""Validate the domain portion of a JID.
|
||||||
|
|
||||||
IP literal addresses are left as-is, if valid. Domain names
|
IP literal addresses are left as-is, if valid. Domain names
|
||||||
@@ -152,7 +153,7 @@ def _validate_domain(domain):
|
|||||||
return domain
|
return domain
|
||||||
|
|
||||||
|
|
||||||
def _validate_resource(resource):
|
def _validate_resource(resource: Optional[str]):
|
||||||
"""Validate the resource portion of a JID.
|
"""Validate the resource portion of a JID.
|
||||||
|
|
||||||
:raises InvalidJID:
|
:raises InvalidJID:
|
||||||
@@ -174,7 +175,7 @@ def _validate_resource(resource):
|
|||||||
return resource
|
return resource
|
||||||
|
|
||||||
|
|
||||||
def _unescape_node(node):
|
def _unescape_node(node: str):
|
||||||
"""Unescape a local portion of a JID.
|
"""Unescape a local portion of a JID.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -199,7 +200,11 @@ def _unescape_node(node):
|
|||||||
return ''.join(unescaped)
|
return ''.join(unescaped)
|
||||||
|
|
||||||
|
|
||||||
def _format_jid(local=None, domain=None, resource=None):
|
def _format_jid(
|
||||||
|
local: Optional[str] = None,
|
||||||
|
domain: Optional[str] = None,
|
||||||
|
resource: Optional[str] = None,
|
||||||
|
):
|
||||||
"""Format the given JID components into a full or bare JID.
|
"""Format the given JID components into a full or bare JID.
|
||||||
|
|
||||||
:param string local: Optional. The local portion of the JID.
|
:param string local: Optional. The local portion of the JID.
|
||||||
@@ -237,12 +242,17 @@ class UnescapedJID:
|
|||||||
|
|
||||||
__slots__ = ('_node', '_domain', '_resource')
|
__slots__ = ('_node', '_domain', '_resource')
|
||||||
|
|
||||||
def __init__(self, node, domain, resource):
|
def __init__(
|
||||||
|
self,
|
||||||
|
node: Optional[str],
|
||||||
|
domain: Optional[str],
|
||||||
|
resource: Optional[str],
|
||||||
|
):
|
||||||
self._node = node
|
self._node = node
|
||||||
self._domain = domain
|
self._domain = domain
|
||||||
self._resource = resource
|
self._resource = resource
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
def __getattribute__(self, name: str):
|
||||||
"""Retrieve the given JID component.
|
"""Retrieve the given JID component.
|
||||||
|
|
||||||
:param name: one of: user, server, domain, resource,
|
:param name: one of: user, server, domain, resource,
|
||||||
@@ -301,7 +311,7 @@ class JID:
|
|||||||
|
|
||||||
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
|
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
|
||||||
|
|
||||||
def __init__(self, jid=None):
|
def __init__(self, jid: Optional[str] = None):
|
||||||
if not jid:
|
if not jid:
|
||||||
self._node = ''
|
self._node = ''
|
||||||
self._domain = ''
|
self._domain = ''
|
||||||
@@ -363,17 +373,17 @@ class JID:
|
|||||||
return self._full
|
return self._full
|
||||||
|
|
||||||
@node.setter
|
@node.setter
|
||||||
def node(self, value):
|
def node(self, value: str):
|
||||||
self._node = _validate_node(value)
|
self._node = _validate_node(value)
|
||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
@domain.setter
|
@domain.setter
|
||||||
def domain(self, value):
|
def domain(self, value: str):
|
||||||
self._domain = _validate_domain(value)
|
self._domain = _validate_domain(value)
|
||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
@bare.setter
|
@bare.setter
|
||||||
def bare(self, value):
|
def bare(self, value: str):
|
||||||
node, domain, resource = _parse_jid(value)
|
node, domain, resource = _parse_jid(value)
|
||||||
assert not resource
|
assert not resource
|
||||||
self._node = node
|
self._node = node
|
||||||
@@ -381,12 +391,12 @@ class JID:
|
|||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
@resource.setter
|
@resource.setter
|
||||||
def resource(self, value):
|
def resource(self, value: str):
|
||||||
self._resource = _validate_resource(value)
|
self._resource = _validate_resource(value)
|
||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
@full.setter
|
@full.setter
|
||||||
def full(self, value):
|
def full(self, value: str):
|
||||||
self._node, self._domain, self._resource = _parse_jid(value)
|
self._node, self._domain, self._resource = _parse_jid(value)
|
||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ class XEP_0030(BasePlugin):
|
|||||||
combination handled by this Slixmpp instance and
|
combination handled by this Slixmpp instance and
|
||||||
no stanzas need to be sent.
|
no stanzas need to be sent.
|
||||||
Otherwise, a disco stanza must be sent to the
|
Otherwise, a disco stanza must be sent to the
|
||||||
remove JID to retrieve the info.
|
remote JID to retrieve the info.
|
||||||
cached -- If true, then look for the disco info data from
|
cached -- If true, then look for the disco info data from
|
||||||
the local cache system. If no results are found,
|
the local cache system. If no results are found,
|
||||||
send the query as usual. The self.use_cache
|
send the query as usual. The self.use_cache
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import with_statement
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from slixmpp import Presence
|
from slixmpp import Presence, Message
|
||||||
from slixmpp.plugins import BasePlugin, register_plugin
|
from slixmpp.plugins import BasePlugin, register_plugin
|
||||||
from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
|
from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
|
||||||
from slixmpp.xmlstream.handler.callback import Callback
|
from slixmpp.xmlstream.handler.callback import Callback
|
||||||
@@ -181,7 +181,7 @@ class XEP_0045(BasePlugin):
|
|||||||
if got_online:
|
if got_online:
|
||||||
self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
|
self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
|
||||||
|
|
||||||
def handle_groupchat_message(self, msg):
|
def handle_groupchat_message(self, msg: Message) -> None:
|
||||||
""" Handle a message event in a muc.
|
""" Handle a message event in a muc.
|
||||||
"""
|
"""
|
||||||
self.xmpp.event('groupchat_message', msg)
|
self.xmpp.event('groupchat_message', msg)
|
||||||
@@ -195,10 +195,14 @@ class XEP_0045(BasePlugin):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def handle_groupchat_subject(self, msg):
|
def handle_groupchat_subject(self, msg: Message) -> None:
|
||||||
""" Handle a message coming from a muc indicating
|
""" Handle a message coming from a muc indicating
|
||||||
a change of subject (or announcing it when joining the room)
|
a change of subject (or announcing it when joining the room)
|
||||||
"""
|
"""
|
||||||
|
# See poezio#3452. A message containing subject _and_ (body or thread)
|
||||||
|
# is not a subject change.
|
||||||
|
if msg['body'] or msg['thread']:
|
||||||
|
return None
|
||||||
self.xmpp.event('groupchat_subject', msg)
|
self.xmpp.event('groupchat_subject', msg)
|
||||||
|
|
||||||
def jid_in_room(self, room, jid):
|
def jid_in_room(self, room, jid):
|
||||||
|
|||||||
@@ -89,31 +89,17 @@ class XEP_0050(BasePlugin):
|
|||||||
self.commands = {}
|
self.commands = {}
|
||||||
|
|
||||||
self.xmpp.register_handler(
|
self.xmpp.register_handler(
|
||||||
Callback("Ad-Hoc Execute",
|
Callback("Ad-Hoc Execute",
|
||||||
StanzaPath('iq@type=set/command'),
|
StanzaPath('iq@type=set/command'),
|
||||||
self._handle_command))
|
self._handle_command))
|
||||||
|
|
||||||
register_stanza_plugin(Iq, Command)
|
register_stanza_plugin(Iq, Command)
|
||||||
register_stanza_plugin(Command, Form, iterable=True)
|
register_stanza_plugin(Command, Form, iterable=True)
|
||||||
|
|
||||||
self.xmpp.add_event_handler('command_execute',
|
self.xmpp.add_event_handler('command', self._handle_command_all)
|
||||||
self._handle_command_start)
|
|
||||||
self.xmpp.add_event_handler('command_next',
|
|
||||||
self._handle_command_next)
|
|
||||||
self.xmpp.add_event_handler('command_cancel',
|
|
||||||
self._handle_command_cancel)
|
|
||||||
self.xmpp.add_event_handler('command_complete',
|
|
||||||
self._handle_command_complete)
|
|
||||||
|
|
||||||
def plugin_end(self):
|
def plugin_end(self):
|
||||||
self.xmpp.del_event_handler('command_execute',
|
self.xmpp.del_event_handler('command', self._handle_command_all)
|
||||||
self._handle_command_start)
|
|
||||||
self.xmpp.del_event_handler('command_next',
|
|
||||||
self._handle_command_next)
|
|
||||||
self.xmpp.del_event_handler('command_cancel',
|
|
||||||
self._handle_command_cancel)
|
|
||||||
self.xmpp.del_event_handler('command_complete',
|
|
||||||
self._handle_command_complete)
|
|
||||||
self.xmpp.remove_handler('Ad-Hoc Execute')
|
self.xmpp.remove_handler('Ad-Hoc Execute')
|
||||||
self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
|
self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
|
||||||
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
|
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
|
||||||
@@ -201,8 +187,27 @@ class XEP_0050(BasePlugin):
|
|||||||
|
|
||||||
def _handle_command(self, iq):
|
def _handle_command(self, iq):
|
||||||
"""Raise command events based on the command action."""
|
"""Raise command events based on the command action."""
|
||||||
|
self.xmpp.event('command', iq)
|
||||||
self.xmpp.event('command_%s' % iq['command']['action'], iq)
|
self.xmpp.event('command_%s' % iq['command']['action'], iq)
|
||||||
|
|
||||||
|
def _handle_command_all(self, iq: Iq) -> None:
|
||||||
|
action = iq['command']['action']
|
||||||
|
sessionid = iq['command']['sessionid']
|
||||||
|
session = self.sessions.get(sessionid)
|
||||||
|
|
||||||
|
if session is None:
|
||||||
|
return self._handle_command_start(iq)
|
||||||
|
|
||||||
|
if action in ('next', 'execute'):
|
||||||
|
return self._handle_command_next(iq)
|
||||||
|
if action == 'prev':
|
||||||
|
return self._handle_command_prev(iq)
|
||||||
|
if action == 'complete':
|
||||||
|
return self._handle_command_complete(iq)
|
||||||
|
if action == 'cancel':
|
||||||
|
return self._handle_command_cancel(iq)
|
||||||
|
return None
|
||||||
|
|
||||||
def _handle_command_start(self, iq):
|
def _handle_command_start(self, iq):
|
||||||
"""
|
"""
|
||||||
Process an initial request to execute a command.
|
Process an initial request to execute a command.
|
||||||
@@ -468,7 +473,7 @@ class XEP_0050(BasePlugin):
|
|||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
def send_command(self, jid, node, ifrom=None, action='execute',
|
def send_command(self, jid, node, ifrom=None, action='execute',
|
||||||
payload=None, sessionid=None, flow=False, **kwargs):
|
payload=None, sessionid=None, flow=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create and send a command stanza, without using the provided
|
Create and send a command stanza, without using the provided
|
||||||
workflow management APIs.
|
workflow management APIs.
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
|
|||||||
sec = now.second
|
sec = now.second
|
||||||
if micro is None:
|
if micro is None:
|
||||||
micro = now.microsecond
|
micro = now.microsecond
|
||||||
if offset is None:
|
if offset in (None, 0):
|
||||||
offset = tzutc()
|
offset = tzutc()
|
||||||
elif not isinstance(offset, dt.tzinfo):
|
elif not isinstance(offset, dt.tzinfo):
|
||||||
offset = tzoffset(None, offset)
|
offset = tzoffset(None, offset)
|
||||||
@@ -177,7 +177,7 @@ def datetime(year=None, month=None, day=None, hour=None,
|
|||||||
sec = now.second
|
sec = now.second
|
||||||
if micro is None:
|
if micro is None:
|
||||||
micro = now.microsecond
|
micro = now.microsecond
|
||||||
if offset is None:
|
if offset in (None, 0):
|
||||||
offset = tzutc()
|
offset = tzutc()
|
||||||
elif not isinstance(offset, dt.tzinfo):
|
elif not isinstance(offset, dt.tzinfo):
|
||||||
offset = tzoffset(None, offset)
|
offset = tzoffset(None, offset)
|
||||||
|
|||||||
@@ -112,9 +112,9 @@ class XEP_0199(BasePlugin):
|
|||||||
try:
|
try:
|
||||||
rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
|
rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
|
||||||
except IqTimeout:
|
except IqTimeout:
|
||||||
log.debug("Did not receive ping back in time." + \
|
log.debug("Did not receive ping back in time. " + \
|
||||||
"Requesting Reconnect.")
|
"Requesting Reconnect.")
|
||||||
self.xmpp.reconnect()
|
self.xmpp.reconnect(0.0, "Ping timeout after %ds" % self.timeout)
|
||||||
else:
|
else:
|
||||||
log.debug('Keepalive RTT: %s' % rtt)
|
log.debug('Keepalive RTT: %s' % rtt)
|
||||||
|
|
||||||
|
|||||||
@@ -123,5 +123,5 @@ class EntityTime(ElementBase):
|
|||||||
if not isinstance(value, dt.datetime):
|
if not isinstance(value, dt.datetime):
|
||||||
date = xep_0082.parse(value)
|
date = xep_0082.parse(value)
|
||||||
date = date.astimezone(tzutc())
|
date = date.astimezone(tzutc())
|
||||||
value = xep_0082.format_datetime(date)[:-1]
|
value = xep_0082.format_datetime(date)
|
||||||
self._set_sub_text('utc', value)
|
self._set_sub_text('utc', value)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class XEP_0202(BasePlugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
"""Start the XEP-0203 plugin."""
|
"""Start the XEP-0202 plugin."""
|
||||||
|
|
||||||
if not self.local_time:
|
if not self.local_time:
|
||||||
def default_local_time(jid):
|
def default_local_time(jid):
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
from slixmpp.plugins.base import register_plugin
|
from slixmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from slixmpp.plugins.xep_0335 import stanza
|
||||||
from slixmpp.plugins.xep_0335.stanza import JSON_Container
|
from slixmpp.plugins.xep_0335.stanza import JSON_Container
|
||||||
from slixmpp.plugins.xep_0335.json_containers import XEP_0335
|
from slixmpp.plugins.xep_0335.json_containers import XEP_0335
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from slixmpp import Message
|
|||||||
from slixmpp.plugins import BasePlugin
|
from slixmpp.plugins import BasePlugin
|
||||||
from slixmpp.xmlstream import register_stanza_plugin
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
from slixmpp.plugins.xep_0335 import JSON_Container
|
from slixmpp.plugins.xep_0335 import JSON_Container
|
||||||
|
from slixmpp.plugins.xep_0335 import stanza
|
||||||
|
|
||||||
|
|
||||||
class XEP_0335(BasePlugin):
|
class XEP_0335(BasePlugin):
|
||||||
|
|||||||
@@ -49,15 +49,17 @@ class XEP_0380(BasePlugin):
|
|||||||
|
|
||||||
register_stanza_plugin(Message, Encryption)
|
register_stanza_plugin(Message, Encryption)
|
||||||
|
|
||||||
def plugin_end(self):
|
|
||||||
self.xmpp.remove_handler('Chat State')
|
|
||||||
|
|
||||||
def session_bind(self, jid):
|
def session_bind(self, jid):
|
||||||
self.xmpp.plugin['xep_0030'].add_feature(Encryption.namespace)
|
self.xmpp.plugin['xep_0030'].add_feature(Encryption.namespace)
|
||||||
|
|
||||||
def has_eme(self, msg):
|
def has_eme(self, msg):
|
||||||
return msg.xml.find('{%s}encryption' % Encryption.namespace) is not None
|
return msg.xml.find('{%s}encryption' % Encryption.namespace) is not None
|
||||||
|
|
||||||
|
def add_eme(self, msg: Message, namespace: str) -> Message:
|
||||||
|
msg['eme']['name'] = self.mechanisms[namespace]
|
||||||
|
msg['eme']['namespace'] = namespace
|
||||||
|
return msg
|
||||||
|
|
||||||
def replace_body_with_eme(self, msg):
|
def replace_body_with_eme(self, msg):
|
||||||
eme = msg['eme']
|
eme = msg['eme']
|
||||||
namespace = eme['namespace']
|
namespace = eme['namespace']
|
||||||
|
|||||||
@@ -361,6 +361,7 @@ class SlixTest(unittest.TestCase):
|
|||||||
# Some plugins require messages to have ID values. Set
|
# Some plugins require messages to have ID values. Set
|
||||||
# this to True in tests related to those plugins.
|
# this to True in tests related to those plugins.
|
||||||
self.xmpp.use_message_ids = False
|
self.xmpp.use_message_ids = False
|
||||||
|
self.xmpp.use_presence_ids = False
|
||||||
|
|
||||||
def make_header(self, sto='',
|
def make_header(self, sto='',
|
||||||
sfrom='',
|
sfrom='',
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ class Cache:
|
|||||||
def store(self, key, value):
|
def store(self, key, value):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def remove(self, key):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
class PerJidCache:
|
class PerJidCache:
|
||||||
def retrieve_by_jid(self, jid, key):
|
def retrieve_by_jid(self, jid, key):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -25,6 +28,9 @@ class PerJidCache:
|
|||||||
def store_by_jid(self, jid, key, value):
|
def store_by_jid(self, jid, key, value):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def remove_by_jid(self, jid, key):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
class MemoryCache(Cache):
|
class MemoryCache(Cache):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
@@ -36,6 +42,11 @@ class MemoryCache(Cache):
|
|||||||
self.cache[key] = value
|
self.cache[key] = value
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def remove(self, key):
|
||||||
|
if key in self.cache:
|
||||||
|
del self.cache[key]
|
||||||
|
return True
|
||||||
|
|
||||||
class MemoryPerJidCache(PerJidCache):
|
class MemoryPerJidCache(PerJidCache):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
@@ -51,6 +62,12 @@ class MemoryPerJidCache(PerJidCache):
|
|||||||
cache[key] = value
|
cache[key] = value
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def remove_by_jid(self, jid, key):
|
||||||
|
cache = self.cache.get(jid, None)
|
||||||
|
if cache is not None and key in cache:
|
||||||
|
del cache[key]
|
||||||
|
return True
|
||||||
|
|
||||||
class FileSystemStorage:
|
class FileSystemStorage:
|
||||||
def __init__(self, encode, decode, binary):
|
def __init__(self, encode, decode, binary):
|
||||||
self.encode = encode if encode is not None else lambda x: x
|
self.encode = encode if encode is not None else lambda x: x
|
||||||
@@ -67,7 +84,10 @@ class FileSystemStorage:
|
|||||||
log.debug('%s not present in cache', key)
|
log.debug('%s not present in cache', key)
|
||||||
except OSError:
|
except OSError:
|
||||||
log.debug('Failed to read %s from cache:', key, exc_info=True)
|
log.debug('Failed to read %s from cache:', key, exc_info=True)
|
||||||
return None
|
except Exception:
|
||||||
|
log.debug('Failed to decode %s from cache:', key, exc_info=True)
|
||||||
|
log.debug('Removing %s entry', key)
|
||||||
|
self._remove(directory, key)
|
||||||
|
|
||||||
def _store(self, directory, key, value):
|
def _store(self, directory, key, value):
|
||||||
filename = os.path.join(directory, key.replace('/', '_'))
|
filename = os.path.join(directory, key.replace('/', '_'))
|
||||||
@@ -79,6 +99,17 @@ class FileSystemStorage:
|
|||||||
except OSError:
|
except OSError:
|
||||||
log.debug('Failed to store %s to cache:', key, exc_info=True)
|
log.debug('Failed to store %s to cache:', key, exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
except Exception:
|
||||||
|
log.debug('Failed to encode %s to cache:', key, exc_info=True)
|
||||||
|
|
||||||
|
def _remove(self, directory, key):
|
||||||
|
filename = os.path.join(directory, key.replace('/', '_'))
|
||||||
|
try:
|
||||||
|
os.remove(filename)
|
||||||
|
except OSError:
|
||||||
|
log.debug('Failed to remove %s from cache:', key, exc_info=True)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
class FileSystemCache(Cache, FileSystemStorage):
|
class FileSystemCache(Cache, FileSystemStorage):
|
||||||
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
|
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
|
||||||
@@ -91,6 +122,9 @@ class FileSystemCache(Cache, FileSystemStorage):
|
|||||||
def store(self, key, value):
|
def store(self, key, value):
|
||||||
return self._store(self.base_dir, key, value)
|
return self._store(self.base_dir, key, value)
|
||||||
|
|
||||||
|
def remove(self, key):
|
||||||
|
return self._remove(self.base_dir, key)
|
||||||
|
|
||||||
class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
|
class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
|
||||||
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
|
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
|
||||||
FileSystemStorage.__init__(self, encode, decode, binary)
|
FileSystemStorage.__init__(self, encode, decode, binary)
|
||||||
@@ -103,3 +137,7 @@ class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
|
|||||||
def store_by_jid(self, jid, key, value):
|
def store_by_jid(self, jid, key, value):
|
||||||
directory = os.path.join(self.base_dir, jid)
|
directory = os.path.join(self.base_dir, jid)
|
||||||
return self._store(directory, key, value)
|
return self._store(directory, key, value)
|
||||||
|
|
||||||
|
def remove_by_jid(self, jid, key):
|
||||||
|
directory = os.path.join(self.base_dir, jid)
|
||||||
|
return self._remove(directory, key)
|
||||||
|
|||||||
@@ -215,6 +215,9 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
#: ``_xmpp-client._tcp`` service.
|
#: ``_xmpp-client._tcp`` service.
|
||||||
self.dns_service = None
|
self.dns_service = None
|
||||||
|
|
||||||
|
#: The reason why we are disconnecting from the server
|
||||||
|
self.disconnect_reason = None
|
||||||
|
|
||||||
#: An asyncio Future being done when the stream is disconnected.
|
#: An asyncio Future being done when the stream is disconnected.
|
||||||
self.disconnected = asyncio.Future()
|
self.disconnected = asyncio.Future()
|
||||||
|
|
||||||
@@ -268,6 +271,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
localhost
|
localhost
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.disconnect_reason = None
|
||||||
self.cancel_connection_attempt()
|
self.cancel_connection_attempt()
|
||||||
if host and port:
|
if host and port:
|
||||||
self.address = (host, int(port))
|
self.address = (host, int(port))
|
||||||
@@ -310,6 +314,8 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
ssl_context = None
|
ssl_context = None
|
||||||
|
|
||||||
await asyncio.sleep(self.connect_loop_wait, loop=self.loop)
|
await asyncio.sleep(self.connect_loop_wait, loop=self.loop)
|
||||||
|
if self._current_connection_attempt is None:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
await self.loop.create_connection(lambda: self,
|
await self.loop.create_connection(lambda: self,
|
||||||
self.address[0],
|
self.address[0],
|
||||||
@@ -323,6 +329,8 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.debug('Connection failed: %s', e)
|
log.debug('Connection failed: %s', e)
|
||||||
self.event("connection_failed", e)
|
self.event("connection_failed", e)
|
||||||
|
if self._current_connection_attempt is None:
|
||||||
|
return
|
||||||
self.connect_loop_wait = self.connect_loop_wait * 2 + 1
|
self.connect_loop_wait = self.connect_loop_wait * 2 + 1
|
||||||
self._current_connection_attempt = asyncio.ensure_future(
|
self._current_connection_attempt = asyncio.ensure_future(
|
||||||
self._connect_routine(),
|
self._connect_routine(),
|
||||||
@@ -398,7 +406,9 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
if self.xml_depth == 0:
|
if self.xml_depth == 0:
|
||||||
# The stream's root element has closed,
|
# The stream's root element has closed,
|
||||||
# terminating the stream.
|
# terminating the stream.
|
||||||
|
self.end_session_on_disconnect = True
|
||||||
log.debug("End of stream received")
|
log.debug("End of stream received")
|
||||||
|
self.disconnect_reason = "End of stream"
|
||||||
self.abort()
|
self.abort()
|
||||||
elif self.xml_depth == 1:
|
elif self.xml_depth == 1:
|
||||||
# A stanza is an XML element that is a direct child of
|
# A stanza is an XML element that is a direct child of
|
||||||
@@ -433,7 +443,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
closure of the TCP connection
|
closure of the TCP connection
|
||||||
"""
|
"""
|
||||||
log.info("connection_lost: %s", (exception,))
|
log.info("connection_lost: %s", (exception,))
|
||||||
self.event("disconnected")
|
self.event("disconnected", self.disconnect_reason or exception and exception.strerror)
|
||||||
if self.end_session_on_disconnect:
|
if self.end_session_on_disconnect:
|
||||||
self.event('session_end')
|
self.event('session_end')
|
||||||
# All these objects are associated with one TCP connection. Since
|
# All these objects are associated with one TCP connection. Since
|
||||||
@@ -453,22 +463,24 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self._current_connection_attempt.cancel()
|
self._current_connection_attempt.cancel()
|
||||||
self._current_connection_attempt = None
|
self._current_connection_attempt = None
|
||||||
|
|
||||||
def disconnect(self, wait=2.0):
|
def disconnect(self, wait=2.0, reason=None):
|
||||||
"""Close the XML stream and wait for an acknowldgement from the server for
|
"""Close the XML stream and wait for an acknowldgement from the server for
|
||||||
at most `wait` seconds. After the given number of seconds has
|
at most `wait` seconds. After the given number of seconds has
|
||||||
passed without a response from the serveur, or when the server
|
passed without a response from the serveur, or when the server
|
||||||
successfully responds with a closure of its own stream, abort() is
|
successfully responds with a closure of its own stream, abort() is
|
||||||
called. If wait is 0.0, this is almost equivalent to calling abort()
|
called. If wait is 0.0, this will call abort() directly without closing
|
||||||
directly.
|
the stream.
|
||||||
|
|
||||||
Does nothing if we are not connected.
|
Does nothing if we are not connected.
|
||||||
|
|
||||||
:param wait: Time to wait for a response from the server.
|
:param wait: Time to wait for a response from the server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.disconnect_reason = reason
|
||||||
self.cancel_connection_attempt()
|
self.cancel_connection_attempt()
|
||||||
if self.transport:
|
if self.transport:
|
||||||
self.send_raw(self.stream_footer)
|
if wait > 0.0:
|
||||||
|
self.send_raw(self.stream_footer)
|
||||||
self.schedule('Disconnect wait', wait,
|
self.schedule('Disconnect wait', wait,
|
||||||
self.abort, repeat=False)
|
self.abort, repeat=False)
|
||||||
|
|
||||||
@@ -484,13 +496,13 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self.disconnected.set_result(True)
|
self.disconnected.set_result(True)
|
||||||
self.disconnected = asyncio.Future()
|
self.disconnected = asyncio.Future()
|
||||||
|
|
||||||
def reconnect(self, wait=2.0):
|
def reconnect(self, wait=2.0, reason="Reconnecting"):
|
||||||
"""Calls disconnect(), and once we are disconnected (after the timeout, or
|
"""Calls disconnect(), and once we are disconnected (after the timeout, or
|
||||||
when the server acknowledgement is received), call connect()
|
when the server acknowledgement is received), call connect()
|
||||||
"""
|
"""
|
||||||
log.debug("reconnecting...")
|
log.debug("reconnecting...")
|
||||||
self.disconnect(wait)
|
self.disconnect(wait, reason)
|
||||||
self.add_event_handler('disconnected', self.connect, disposable=True)
|
self.add_event_handler('disconnected', lambda event: self.connect(), disposable=True)
|
||||||
|
|
||||||
def configure_socket(self):
|
def configure_socket(self):
|
||||||
"""Set timeout and other options for self.socket.
|
"""Set timeout and other options for self.socket.
|
||||||
|
|||||||
74
tests/test_cache.py
Normal file
74
tests/test_cache.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import unittest
|
||||||
|
from slixmpp.test import SlixTest
|
||||||
|
from slixmpp.util import (
|
||||||
|
MemoryCache, MemoryPerJidCache,
|
||||||
|
FileSystemCache, FileSystemPerJidCache
|
||||||
|
)
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
class TestCacheClass(SlixTest):
|
||||||
|
|
||||||
|
def testMemoryCache(self):
|
||||||
|
cache = MemoryCache()
|
||||||
|
|
||||||
|
cache.store("test", "test_value")
|
||||||
|
self.assertEqual(cache.retrieve("test"), "test_value")
|
||||||
|
self.assertEqual(cache.retrieve("test2"), None)
|
||||||
|
|
||||||
|
cache.remove("test")
|
||||||
|
self.assertEqual(cache.retrieve("test"), None)
|
||||||
|
|
||||||
|
def testMemoryPerJidcache(self):
|
||||||
|
cache = MemoryPerJidCache()
|
||||||
|
|
||||||
|
cache.store_by_jid("test@example.com", "test", "test_value")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve_by_jid("test@example.com", "test"),
|
||||||
|
"test_value"
|
||||||
|
)
|
||||||
|
|
||||||
|
cache.remove_by_jid("test@example.com", "test")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve_by_jid("test@example.com", "test"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
def testFileSystemCache(self):
|
||||||
|
def failing_decode(value):
|
||||||
|
if value == "failme":
|
||||||
|
raise Exception("you failed")
|
||||||
|
return value
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
cache = FileSystemCache(tmpdir, "test", decode=failing_decode)
|
||||||
|
cache.store("test", "test_value")
|
||||||
|
cache.store("test2", "failme")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve("test"),
|
||||||
|
"test_value"
|
||||||
|
)
|
||||||
|
cache.remove("test")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve("test"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve("test2"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
def testFileSystemPerJidCache(self):
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
cache = FileSystemPerJidCache(tmpdir, "test")
|
||||||
|
cache.store_by_jid("test@example.com", "test", "test_value")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve_by_jid("test@example.com", "test"),
|
||||||
|
"test_value"
|
||||||
|
)
|
||||||
|
cache.remove_by_jid("test@example.com", "test")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve_by_jid("test@example.com", "test"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestCacheClass)
|
||||||
Reference in New Issue
Block a user