diff --git a/.travis.yml b/.travis.yml
index bbb1f2d4d2c78c6ed42e85816e3ceeb466b0e53c..f700f4342f19a4d257db15c34064ff4ef5996060 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,6 +9,7 @@ python:
 script:
  - python compile_definitions.py
  - python setup.py nosetests
+ - python -m stress_tests
 install:
  - pip install -r requirements.txt
  - pip install --force-reinstall "coverage>=4.0,<4.4" codeclimate-test-reporter
diff --git a/Vagrantfile b/Vagrantfile
index 81775013ef6857180bb2d2a31248479967270264..efc48bec9f8a61059d0aa8d3e7db6140ba4096c3 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -3,7 +3,7 @@
 
 Vagrant.configure("2") do |config|
 
-  config.vm.box = "debian/contrib-jessie64"
+  config.vm.box = "debian/contrib-stretch64"
 
   # Rabbit MQ management
   config.vm.network "forwarded_port", guest: 15672, host: 15672, auto_correct: true
@@ -12,31 +12,33 @@ Vagrant.configure("2") do |config|
   config.vm.network "forwarded_port", guest: 80, host: 8765
 
   config.vm.provision "shell", inline: <<-SHELL
-     apt-get update
-
-     # Python
-     apt-get install -y htop curl python python-setuptools python-pip python-dev build-essential rabbitmq-server python3 python3-pip python3-setuptools
-     pip install --upgrade pip setuptools
-     pip3 install --upgrade pip setuptools
-
-     /usr/lib/rabbitmq/bin/rabbitmq-plugins enable rabbitmq_management
-     sudo service rabbitmq-server restart
-     rabbitmqctl add_user user user
-     rabbitmqctl set_permissions -p / user ".*" ".*" ".*"
-     rabbitmqctl set_permissions -p / guest ".*" ".*" ".*"
-     rabbitmqctl set_user_tags user administrator
-
-     # Install deps
-     pip install -r /vagrant/requirements.txt
-     pip install nose coverage
-     pip3 install -r /vagrant/requirements.txt
-     pip3 install nose coverage
-
-     # HTTP server for viewing coverage reports
+    apt-get update
+    
+    # Python
+    apt-get install -y htop curl python python-setuptools python-pip python-dev build-essential rabbitmq-server python3 python3-pip python3-setuptools
+    sudo pip install --upgrade pip setuptools
+    sudo pip3 install --upgrade pip setuptools
+    
+    /usr/lib/rabbitmq/bin/rabbitmq-plugins enable rabbitmq_management
+    sudo service rabbitmq-server restart
+    rabbitmqctl add_user user user
+    rabbitmqctl set_permissions -p / user ".*" ".*" ".*"
+    rabbitmqctl set_permissions -p / guest ".*" ".*" ".*"
+    rabbitmqctl set_user_tags user administrator
+    
+    # Install deps
+    sudo pip install -r /vagrant/requirements.txt
+    sudo pip install nose coverage mock
+    sudo pip3 install -r /vagrant/requirements.txt
+    sudo pip3 install nose coverage mock
+    
+    sudo pip3 install -r /vagrant/stress_tests/requirements.txt
+    
+    # HTTP server for viewing coverage reports
     apt-get -y install nginx
     rm -rf /var/www/html
     ln -s /vagrant/htmlcov /var/www/html
-
+    
     # .bashrc for default user
     echo """# .bashrc
     cd /vagrant""" > /home/vagrant/.bashrc
diff --git a/coolamqp/objects.py b/coolamqp/objects.py
index c6f0f3473db957867f19fe3927e5711e6c55ff87..458b4afdbd02102a996ec154937621baabcb5bb5 100644
--- a/coolamqp/objects.py
+++ b/coolamqp/objects.py
@@ -4,6 +4,7 @@ Core objects used in CoolAMQP
 """
 import logging
 import uuid
+import typing as tp
 
 import six
 
@@ -104,15 +105,17 @@ class ReceivedMessage(Message):
     and .nack() are no-ops.
     """
 
-    def __init__(self, body, exchange_name, routing_key,
-                 properties=None,
-                 delivery_tag=None,
-                 ack=None,
-                 nack=None):
+    def __init__(self, body,            # type: tp.Union[six.binary_type, memoryview, tp.List[memoryview]]
+                 exchange_name,         # type: memoryview
+                 routing_key,           # type: memoryview
+                 properties=None,       # type: MessageProperties
+                 delivery_tag=None,     # type: int
+                 ack=None,              # type: tp.Callable[[], None]
+                 nack=None):            # type: tp.Callable[[], None]
         """
         :param body: message body. A stream of octets.
         :type body: str (py2) or bytes (py3) or a list of memoryviews, if
-            particular disabled-by-default option is turned on.
+            particular disabled-by-default option is turned on, or a single memoryview
         :param exchange_name: name of exchange this message was submitted to
         :type exchange_name: memoryview
         :param routing_key: routing key with which this message was sent
@@ -124,9 +127,11 @@ class ReceivedMessage(Message):
             this message
         :param ack: a callable to call when you want to ack (via basic.ack)
             this message. None if received by the no-ack mechanism
+        :type ack: Callable[[], None]
         :param nack: a callable to call when you want to nack
             (via basic.reject) this message. None if received by the no-ack
              mechanism
+        :type nack: Callable[[], None]
         """
         Message.__init__(self, body, properties=properties)
 
@@ -231,20 +236,20 @@ class NodeDefinition(object):
         """
         Create a cluster node definition.
 
-            a = NodeDefinition(host='192.168.0.1', user='admin', password='password',
-                            virtual_host='vhost')
+        >>> a = NodeDefinition(host='192.168.0.1', user='admin', password='password',
+        >>>                   virtual_host='vhost')
 
         or
 
-            a = NodeDefinition('192.168.0.1', 'admin', 'password')
+        >>> a = NodeDefinition('192.168.0.1', 'admin', 'password')
             
         or
         
-            a = NodeDefinition('amqp://user:password@host/virtual_host')
+        >>> a = NodeDefinition('amqp://user:password@host/virtual_host')
         
         or
         
-            a = NodeDefinition('amqp://user:password@host:port/virtual_host', hearbeat=20)
+        >>> a = NodeDefinition('amqp://user:password@host:port/virtual_host', hearbeat=20)
 
         AMQP connection string may be either bytes or str/unicode
         
diff --git a/docs/caveats.md b/docs/caveats.rst
similarity index 51%
rename from docs/caveats.md
rename to docs/caveats.rst
index 514f38432264a156efa5c0b342ea261054d56ff7..658396210feaddbf19ac617b89b1bf2c860e10ca 100644
--- a/docs/caveats.md
+++ b/docs/caveats.rst
@@ -1,28 +1,30 @@
-# Caveats
+Caveats
+=======
 
 Things to look out for
 
-## memoryviews
+memoryviews
+-----------
 
 Since CoolAMQP tries to be fast, it uses memoryviews everywhere. _ReceivedMessage_ properties, and message
 properties therefore, are memoryviews. So, it you wanted to read the routing key a message was sent with,
 or message's encoding, you should do:
 
-```python
-received_msg.routing_key.to_bytes()
-received_msg.properties.content_encoding.to_bytes()
-```
+::
 
-Only the _body_ property of the message will be a byte object (and not even that it you explicitly ask otherwise).
+    received_msg.routing_key.to_bytes()
+    received_msg.properties.content_encoding.to_bytes()
+
+Only the **body** property of the message will be a byte object (and not even that it you explicitly ask otherwise).
 
 Note that YOU, when sending messages, should not use memoryviews. Pass proper byte objects and text objects
 as required.
 
-_AMQPError_'s returned to you via futures will also have memoryviews as _reply_text_!
+**AMQPError**'s returned to you via futures will also have memoryviews as **reply_text**!
 
-It was considered whether to unserialize short fields, such as _routing_key_ or _exchange_, but it was decided against. 
+It was considered whether to unserialize short fields, such as **routing_key** or **exchange**, but it was decided against.
 Creating a new memoryview carries at least much overhead as an empty string, but there's no need to copy.
 Plus, it's not known whether you will use these strings at all!
 
-If you need to, you got memoryviews. Plus they support the \__eq__ protocol, which should cover most
+If you need to, you got memoryviews. Plus they support the **__eq__** protocol, which should cover most
 use cases without even converting.
diff --git a/docs/conf.py b/docs/conf.py
index d9f72fe9ad9f88e6d135865dce4cdb8a7ab8d757..2edc8d78d442ca4478ebb1087e133ecb4feb4536 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -16,13 +16,11 @@
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 #
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('.'))
-from recommonmark.parser import CommonMarkParser
+import os
+import sys
+sys.path.insert(0, os.path.abspath('..'))
 
 source_parsers = {
-    '.md': CommonMarkParser
 }
 
 # -- General configuration ------------------------------------------------
@@ -42,14 +40,14 @@ templates_path = ['_templates']
 # The suffix(es) of source filenames.
 # You can specify multiple suffix as a list of string:
 #
-source_suffix = ['.rst', '.md']
+source_suffix = ['.rst']
 
 # The master toctree document.
 master_doc = 'index'
 
 # General information about the project.
 project = u'CoolAMQP'
-copyright = u'2016-2017, DMS Serwis s.c.'
+copyright = u'2016-2019, SMOK Serwis s.c.'
 author = u'DMS Serwis s.c.'
 
 # The version info for the project you're documenting, acts as replacement for
diff --git a/docs/tutorial.md b/docs/tutorial.rst
similarity index 62%
rename from docs/tutorial.md
rename to docs/tutorial.rst
index 57e3704b7d20d363be94cfdef766286ad9d941d3..e0fc3e6a6bd4585dcc54b6bc57f979d84ad0e96d 100644
--- a/docs/tutorial.md
+++ b/docs/tutorial.rst
@@ -1,4 +1,6 @@
-# Tutorial
+========
+Tutorial
+========
 
 If you want to connect to an AMQP broker, you need:
 * its address (and port)
@@ -10,36 +12,42 @@ in the future, you should define the nodes first. You can do it using _NodeDefin
 See NodeDefinition's documentation for alternative ways to do this, but here we will use
 the AMQP connection string.
 
-```python
-from coolamqp.objects import NodeDefinition
+.. autoclass:: coolamqp.objects.NodeDefinition
+    :members:
 
-node = NodeDefinition('amqp://user@password:host/vhost')
-```
+::
 
-_Cluster_ instances are used to interface with the cluster (or a single broker). It
+    from coolamqp.objects import NodeDefinition
+
+    node = NodeDefinition('amqp://user@password:host/vhost')
+
+Cluster instances are used to interface with the cluster (or a single broker). It
 accepts a list of nodes:
 
-```python
-from coolamqp.clustering import Cluster
-cluster = Cluster([node])
-cluster.start(wait=True)
-```
+::
+    from coolamqp.clustering import Cluster
+    cluster = Cluster([node])
+    cluster.start(wait=True)
+
+.. autoclass:: coolamqp.clustering.Cluster
+    :members:
 
-_wait=True_ will block until connection is completed. After this, you can use other methods.
+*wait=True* will block until connection is completed. After this, you can use other methods.
 
-## Publishing and consuming
+Publishing and consuming
+------------------------
 
 Connecting is boring. After we do, we want to do something! Let's try sending a message, and receiving it. To do that,
 you must first define a queue, and register a consumer.
 
-```python
-from coolamqp.objects import Queue
+::
 
-queue = Queue(u'my_queue', auto_delete=True, exclusive=True)
+    from coolamqp.objects import Queue
 
-consumer, consume_confirm = cluster.consume(queue, no_ack=False)
-consume_confirm.result()    # wait for consuming to start
-```
+    queue = Queue(u'my_queue', auto_delete=True, exclusive=True)
+
+    consumer, consume_confirm = cluster.consume(queue, no_ack=False)
+    consume_confirm.result()    # wait for consuming to start
 
 This will create an auto-delete and exclusive queue. After than, a consumer will be registered for this queue.
 _no_ack=False_ will mean that we have to manually confirm messages. 
@@ -53,12 +61,15 @@ when AMQP _basic.consume-ok_ is received.
 
 To send a message we need to construct it first, and later publish:
 
-```python
-from coolamqp.objects import Message
+::
+
+    from coolamqp.objects import Message
+
+    msg = Message(b'hello world', properties=Message.Properties())
+    cluster.publish(msg, routing_key=u'my_queue')
 
-msg = Message(b'hello world', properties=Message.Properties())
-cluster.publish(msg, routing_key=u'my_queue')
-```
+.. autoclass:: coolamqp.objects.Message
+    :members:
 
 This creates a message with no properties, and sends it through default (direct) exchange to our queue.
 Note that CoolAMQP simply considers your messages to be bags of bytes + properties. It will not modify them,
diff --git a/requirements.txt b/requirements.txt
index 897b05d1921931d68d0b05ef436e47211ec1df65..837c43015ff26d92692f31060f1d748c22d831c5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
 six
 monotonic
 futures
+typing
diff --git a/setup.cfg b/setup.cfg
index 079e26a411dce271c2faafa3f85156d04dd8d881..4553be3f616520757b2fb049167f0a1eb8b29cf9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,7 @@
 [metadata]
 description-file = README.md
 name = CoolAMQP
-version = 0.97a1
+version = 0.97a2
 license = MIT License
 classifiers = 
     Programming Language :: Python