Skip to content
Permalink
Browse files

Prepared for aioxmpp 0.10. Added how to make plugins doc.

  • Loading branch information...
javipalanca committed Jul 30, 2018
1 parent e7f968c commit f0d2dcf1c1f3be226b115feea573d15b307ed7ea
Showing with 175 additions and 31 deletions.
  1. +3 −2 AUTHORS.rst
  2. +6 −1 CONTRIBUTING.rst
  3. +5 −0 README.rst
  4. +1 −1 docs/conf.py
  5. +120 −0 docs/extending.rst
  6. +1 −0 docs/index.rst
  7. +2 −2 examples/send_and_recv.py
  8. +1 −1 requirements.txt
  9. +10 −9 spade/agent.py
  10. +11 −0 tests/test_agent.py
  11. +15 −15 tests/test_presence.py
@@ -5,9 +5,10 @@ Credits
Development Lead
----------------

* Javi Palanca <jpalanca@gmail.com>
* Javi Palanca <https://github.com/javipalanca>

Contributors
------------

None yet. Why not be the first?
* Sergio Alemany <https://github.com/Gersiete>

@@ -12,6 +12,11 @@ You can contribute in many ways:
Types of Contributions
----------------------

Implement Plugins
~~~~~~~~~~~~~~~~~

SPADE can be extended by means of plugins. See how to develop one at :ref:`Extending SPADE with plugins`.

Report Bugs
~~~~~~~~~~~

@@ -110,5 +115,5 @@ Tips

To run a subset of tests::

$ py.test tests.test_spade
$ py.test tests.test_agent

@@ -40,6 +40,11 @@ Features
* Agent model based on behaviours
* Supports FIPA metadata using XMPP Data Forms (XEP-0004_: Data Forms)

Plugins
-------

None yet.

Credits
---------

@@ -40,7 +40,7 @@

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.autosectionlabel']

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -0,0 +1,120 @@
============================
Extending SPADE with plugins
============================

This release of SPADE is designed as a very light version of the platform (compared with SPADE<3.0) which provides only
the core features that a MAS platform should have. This implies that some of the features that were provided by previous
versions of the platform are now not included.

*How makes that sense?* Well, all that previous features are not lost, but
are going to be turned into plugins that you can connect to your MAS application.

This way it is very easy to add new features to SPADE without disturbing the core development.

We have planned three different ways to design plugins for the SPADE platform, but of course we are open to suggestions.

.. warning::
A plugin needs to complain with some requirements to be accepted as a SPADE plugin and be listed as an official
plugin on the main page:
#. It must be open source (of course!) and published in PyPi.
#. The package must be called spade-* (e.g.: spade-bdi, spade-owl, etc.) and be imported as ``import spade_*``.
#. It must be tested.
#. It must follow the `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_.

You can develop *new behaviours*, *new mixins* that modify behaviours, and of course *new libraries* that your agents
can use inside your behaviours. Let's see some examples of each of these ones:


New Behaviours
--------------

Developing new behaviours is as easy as creating a new class that inherits from ``spade.behaviour.CyclicBehaviour`` (or
any of its subclassed behaviours) and overload the methods that are needed. Pay atention to the methods that are related
with the control flow of a behaviour like ``_step``, ``done`` and ``_run``. And remenber that you *should not* overload
the methods that are reserved for the user to be overloaded: ``on_start``, ``run`` and ``on_end``.

Example::

class BDIBehaviour(spade.behaviour.PeriodicBehaviour):

async def _step(self):
# the bdi stuff

def add_belief(self, ...):
...
def add_desire(self, ...):
...
def add_intention(self, ...):
...
def done(self):
# the done evaluation

...


New Mixins
----------

Some cases you don't want to add a new behaviour, but to add new features to current behaviours. This can be done by
means of *mixins*. A mixin is a class that a behaviour can inherit from, in addition to the original parent class,
making use of the multiple inheritance of python. This way, when we are creating our agent and we implement its
behaviour which is (for example) a cyclic behaviour and we want to add this behaviour a feature that is provided by a
plugin called ``spade-p2p`` that allows the agent to send P2P messages (by modifying the send and receive methods of the
behaviour) we should do the following::

from spade_p2p import P2PMixin

class MyNewBehaviour(P2PMixin, CyclicBehaviour):
...
async def run(self):
...
self.send(my_message, p2p=True)
...


.. warning::
The order of your mixins is important! The base behaviour class **must** be **always** the last one in the
method resolution order.

.. hint::
Remember that if you need to call the parent function of the base behaviour (or any other mixin in the method
resolution order), you must use the ``super()`` function (see the following example).

To develop this example mixin you should do the following::

class P2PMixin(object):
async def send(self, msg, p2p=False):
if p2p:
await self.send_p2p(msg)
else:
await super().send(msg)


async def senf_p2p(self, msg):
...





New Libraries
-------------

Finally, the easiest way to add new features to your agents is by means of *libraries*. If you want your agents to
support, for example, the OWL content language, you don't need to change spade, just make a library that handles it.
Example::

from spade_owl import parse as owl_parse
from spade_owl import dump as owl_dump

class MyBehaviour(spade.behaviour.CyclicBehaviour):
async def run(self):
msg = await self.receive()

owl_content = owl_parse(msg.content)
# do wat you want with the owl content

reply.content = owl_dump(...my owl reply...)

await self.send(reply)

@@ -14,6 +14,7 @@ Contents:
agents
behaviours
presence
extending
modules
contributing
authors
@@ -60,10 +60,10 @@ def setup(self):
recv_passwd = getpass.getpass()

receiveragent = ReceiverAgent(recv_jid, recv_passwd)
receiveragent.start()
receiveragent.start(auto_register=True)
time.sleep(2) # wait for receiver agent to be prepared. In next sections we'll use presence notification.
senderagent = SenderAgent(recv_jid, sender_jid, sender_passwd)
senderagent.start()
senderagent.start(auto_register=True)

while receiveragent.is_alive():
try:
@@ -1,5 +1,5 @@
multidict<5.0,>=2.0
aioxmpp==0.9.1
aioxmpp>=0.10
aiohttp>=2.3.1
aiohttp_jinja2>=0.14.0
jinja2>=2.9.6
@@ -5,7 +5,7 @@
from threading import Thread, Event

import aioxmpp
# import aioxmpp.ibr as ibr # TODO: will be activated with aioxmpp 0.10
import aioxmpp.ibr as ibr
from aioxmpp.dispatcher import SimpleMessageDispatcher

from spade.message import Message
@@ -58,13 +58,11 @@ def start(self, auto_register=False):

self.setup()

def register(self):
# TODO: will be activated with aioxmpp 0.10
pass
# metadata = aioxmpp.make_security_layer(None, no_verify=not self.verify_security)
# _, stream, features = self.loop.run_until_complete(aioxmpp.node.connect_xmlstream(self.jid, metadata))
# query = ibr.Query(self.jid.localpart, self.password)
# self.loop.run_until_complete(ibr.register(stream, query))
def register(self): # pragma: no cover
metadata = aioxmpp.make_security_layer(None, no_verify=not self.verify_security)
_, stream, features = self.loop.run_until_complete(aioxmpp.node.connect_xmlstream(self.jid, metadata))
query = ibr.Query(self.jid.localpart, self.password)
self.loop.run_until_complete(ibr.register(stream, query))

def setup(self):
"""
@@ -237,5 +235,8 @@ def run(self):
def finalize(self):
aexit = self.conn_coro.__aexit__(*sys.exc_info())
future = asyncio.run_coroutine_threadsafe(aexit, loop=self.loop)
future.result()
try:
future.result(timeout=5)
except TimeoutError:
logger.error("Could not disconnect from server.")
self.loop.call_soon_threadsafe(self.loop.stop)
@@ -85,3 +85,14 @@ def test_get__none():
def test_client():
agent = make_connected_agent()
assert type(agent.client) == PresenceManagedClient


def test_register():
agent = make_connected_agent()
agent.register = Mock()

agent.start(auto_register=True)

assert len(agent.register.mock_calls) == 1

agent.stop()
@@ -234,11 +234,11 @@ def test_subscribe(jid):
peer_jid = str(jid)
agent = make_presence_connected_agent()

agent.aiothread.client.stream.enqueue = Mock()
agent.aiothread.client.enqueue = Mock()
agent.presence.subscribe(peer_jid)

assert agent.aiothread.client.stream.enqueue.mock_calls
arg = agent.aiothread.client.stream.enqueue.call_args[0][0]
assert agent.aiothread.client.enqueue.mock_calls
arg = agent.aiothread.client.enqueue.call_args[0][0]

assert arg.to == jid.bare()
assert arg.type_ == PresenceType.SUBSCRIBE
@@ -248,11 +248,11 @@ def test_unsubscribe(jid):
peer_jid = str(jid)
agent = make_presence_connected_agent()

agent.aiothread.client.stream.enqueue = Mock()
agent.aiothread.client.enqueue = Mock()
agent.presence.unsubscribe(peer_jid)

assert agent.aiothread.client.stream.enqueue.mock_calls
arg = agent.aiothread.client.stream.enqueue.call_args[0][0]
assert agent.aiothread.client.enqueue.mock_calls
arg = agent.aiothread.client.enqueue.call_args[0][0]

assert arg.to == jid.bare()
assert arg.type_ == PresenceType.UNSUBSCRIBE
@@ -262,11 +262,11 @@ def test_approve(jid):
peer_jid = str(jid)
agent = make_presence_connected_agent()

agent.aiothread.client.stream.enqueue = Mock()
agent.aiothread.client.enqueue = Mock()
agent.presence.approve(peer_jid)

assert agent.aiothread.client.stream.enqueue.mock_calls
arg = agent.aiothread.client.stream.enqueue.call_args[0][0]
assert agent.aiothread.client.enqueue.mock_calls
arg = agent.aiothread.client.enqueue.call_args[0][0]

assert arg.to == jid.bare()
assert arg.type_ == PresenceType.SUBSCRIBED
@@ -316,13 +316,13 @@ def test_on_subscribe(jid):
def test_on_subscribe_approve_all(jid):
agent = make_presence_connected_agent()
agent.presence.approve_all = True
agent.aiothread.client.stream.enqueue = Mock()
agent.aiothread.client.enqueue = Mock()

stanza = Presence(from_=jid, type_=PresenceType.SUBSCRIBE)
agent.presence.roster.handle_subscribe(stanza)

assert agent.aiothread.client.stream.enqueue.mock_calls
arg = agent.aiothread.client.stream.enqueue.call_args[0][0]
assert agent.aiothread.client.enqueue.mock_calls
arg = agent.aiothread.client.enqueue.call_args[0][0]

assert arg.to == jid.bare()
assert arg.type_ == PresenceType.SUBSCRIBED
@@ -355,13 +355,13 @@ def test_on_unsubscribe(jid):
def test_on_unsubscribe_approve_all(jid):
agent = make_presence_connected_agent()
agent.presence.approve_all = True
agent.aiothread.client.stream.enqueue = Mock()
agent.aiothread.client.enqueue = Mock()

stanza = Presence(from_=jid, type_=PresenceType.UNSUBSCRIBE)
agent.presence.roster.handle_unsubscribe(stanza)

assert agent.aiothread.client.stream.enqueue.mock_calls
arg = agent.aiothread.client.stream.enqueue.call_args[0][0]
assert agent.aiothread.client.enqueue.mock_calls
arg = agent.aiothread.client.enqueue.call_args[0][0]

assert arg.to == jid.bare()
assert arg.type_ == PresenceType.UNSUBSCRIBED

0 comments on commit f0d2dcf

Please sign in to comment.
You can’t perform that action at this time.