IGNITE-14418 Add async client documentation, update examples

This closes #29
diff --git a/README.md b/README.md
index 47bd712..f44276f 100644
--- a/README.md
+++ b/README.md
@@ -99,8 +99,8 @@
 $ pip install -r requirements/install.txt -r requirements/tests.txt
 ```
 
-Also, you'll need to have a binary release of Ignite with lib4j2 enabled and
-`IGNITE_HOME` properly set: 
+Also, you'll need to have a binary release of Ignite with `log4j2` enabled and to set
+`IGNITE_HOME` environment variable: 
 ```bash
 $ cd <ignite_binary_release>
 $ export IGNITE_HOME=$(pwd)
@@ -114,14 +114,6 @@
 ```bash
 $ pytest --examples 
 ```
-### Run with ssl and not encrypted key
-```bash
-$ pytest --use-ssl=True --ssl-certfile=./tests/ssl/client_full.pem
-```
-### Run with ssl and password-protected key
-```bash
-$ pytest --use-ssl=True --ssl-certfile=./tests/config/ssl/client_with_pass_full.pem --ssl-keyfile-password=654321
-```
 
 If you need to change the connection parameters, see the documentation on
 [testing](https://apache-ignite-binary-protocol-client.readthedocs.io/en/latest/readme.html#testing).
diff --git a/docs/async_examples.rst b/docs/async_examples.rst
new file mode 100644
index 0000000..363599a
--- /dev/null
+++ b/docs/async_examples.rst
@@ -0,0 +1,151 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+.. _async_examples_of_usage:
+
+============================
+Asynchronous client examples
+============================
+File: `async_key_value.py`_.
+
+Basic usage
+-----------
+Asynchronous client and cache (:py:class:`~pyignite.aio_client.AioClient` and :py:class:`~pyignite.aio_cache.AioCache`)
+has mostly the same API as synchronous ones (:py:class:`~pyignite.client.Client` and :py:class:`~pyignite.cache.Cache`).
+But there is some peculiarities.
+
+Basic key-value
+===============
+Firstly, import dependencies.
+
+.. literalinclude:: ../examples/async_key_value.py
+  :language: python
+  :lines: 18
+
+Let's connect to cluster and perform key-value queries.
+
+.. literalinclude:: ../examples/async_key_value.py
+  :language: python
+  :dedent: 4
+  :lines: 23-38
+
+Scan
+====
+The :py:meth:`~pyignite.aio_cache.AioŠ”ache.scan` method returns :py:class:`~pyignite.cursors.AioScanCursor`,
+that yields the resulting rows.
+
+.. literalinclude:: ../examples/async_key_value.py
+  :language: python
+  :dedent: 4
+  :lines: 39-50
+
+
+File: `async_sql.py`_.
+
+SQL
+---
+
+First let us establish a connection.
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :dedent: 4
+  :lines: 197-198
+
+Then create tables. Begin with `Country` table, than proceed with related
+tables `City` and `CountryLanguage`.
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :lines: 25-42, 51-59, 67-74
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :dedent: 4
+  :lines: 199-205
+
+Create indexes.
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :lines: 60-62, 75-77
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :dedent: 8
+  :lines: 207-209
+
+Fill tables with data.
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :lines: 43-50, 63-66, 78-81
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :dedent: 8
+  :lines: 212-223
+
+Now let us answer some questions.
+
+What are the 10 largest cities in our data sample (population-wise)?
+====================================================================
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :dedent: 8
+  :lines: 225-243
+
+The :py:meth:`~pyignite.aio_client.AioClient.sql` method returns :py:class:`~pyignite.cursors.AioSqlFieldsCursor`,
+that yields the resulting rows.
+
+What are the 10 most populated cities throughout the 3 chosen countries?
+========================================================================
+
+If you set the `include_field_names` argument to `True`, the
+:py:meth:`~pyignite.client.Client.sql` method will generate a list of
+column names as a first yield. Unfortunately, there is no async equivalent of `next` but
+you can await :py:meth:`__anext__()`
+of :py:class:`~pyignite.cursors.AioSqlFieldsCursor`
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :dedent: 8
+  :lines: 246-271
+
+Display all the information about a given city
+==============================================
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :dedent: 8
+  :lines: 273-288
+
+Finally, delete the tables used in this example with the following queries:
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :lines: 83
+
+.. literalinclude:: ../examples/async_sql.py
+  :language: python
+  :dedent: 8
+  :lines: 290-297
+
+
+
+
+.. _async_key_value.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/async_key_value.py
+.. _async_sql.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/async_sql.py
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 8c498aa..31e4fa1 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,4 +1,19 @@
-# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
 #
 # Configuration file for the Sphinx documentation builder.
 #
@@ -14,19 +29,16 @@
 #
 import os
 import sys
+
+
 sys.path.insert(0, os.path.abspath('../'))
 
 
 # -- Project information -----------------------------------------------------
 
 project = 'Apache Ignite binary client Python API'
-copyright = '2018, Apache Software Foundation (ASF)'
-author = 'Dmitry Melnichuk'
-
-# The short X.Y version
-version = ''
-# The full version, including alpha/beta/rc tags
-release = '0.1.0'
+copyright = '2021, Apache Software Foundation (ASF)'
+author = ''
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/docs/datatypes/cache_props.rst b/docs/datatypes/cache_props.rst
index 03443b9..3cabbe6 100644
--- a/docs/datatypes/cache_props.rst
+++ b/docs/datatypes/cache_props.rst
@@ -31,7 +31,9 @@
 | name                                  | value    | type     |                                                       |
 +=======================================+==========+==========+=======================================================+
 | Read/write cache properties, used to configure cache via :py:meth:`~pyignite.client.Client.create_cache` or         |
-| :py:meth:`~pyignite.client.Client.get_or_create_cache`                                                              |
+| :py:meth:`~pyignite.client.Client.get_or_create_cache` of :py:class:`~pyignite.client.Client`                       |
+| (:py:meth:`~pyignite.aio_client.AioClient.create_cache` or                                                          |
+| :py:meth:`~pyignite.aio_client.AioClient.get_or_create_cache` of :py:class:`~pyignite.aio_client.AioClient`).       |
 +---------------------------------------+----------+----------+-------------------------------------------------------+
 | PROP_NAME                             |        0 | str      | Cache name. This is the only *required* property.     |
 +---------------------------------------+----------+----------+-------------------------------------------------------+
@@ -96,10 +98,6 @@
 +---------------------------------------+----------+----------+-------------------------------------------------------+
 | PROP_STATISTICS_ENABLED               |      406 | bool     | Statistics enabled                                    |
 +---------------------------------------+----------+----------+-------------------------------------------------------+
-| Read-only cache properties. Can not be set, but only retrieved via :py:meth:`~pyignite.cache.Cache.settings`        |
-+---------------------------------------+----------+----------+-------------------------------------------------------+
-| PROP_INVALIDATE                       |       -1 | bool     | Invalidate                                            |
-+---------------------------------------+----------+----------+-------------------------------------------------------+
 
 Query entity
 ------------
diff --git a/docs/examples.rst b/docs/examples.rst
index 4b8c7e3..0379330 100644
--- a/docs/examples.rst
+++ b/docs/examples.rst
@@ -37,28 +37,32 @@
 
 .. literalinclude:: ../examples/get_and_put.py
   :language: python
-  :lines: 21
+  :dedent: 4
+  :lines: 20
 
 Put value in cache
 ==================
 
 .. literalinclude:: ../examples/get_and_put.py
   :language: python
-  :lines: 23
+  :dedent: 4
+  :lines: 22
 
 Get value from cache
 ====================
 
 .. literalinclude:: ../examples/get_and_put.py
   :language: python
-  :lines: 25-29
+  :dedent: 4
+  :lines: 24-28
 
 Get multiple values from cache
 ==============================
 
 .. literalinclude:: ../examples/get_and_put.py
   :language: python
-  :lines: 31-36
+  :dedent: 4
+  :lines: 30-35
 
 Type hints usage
 ================
@@ -66,6 +70,7 @@
 
 .. literalinclude:: ../examples/type_hints.py
   :language: python
+  :dedent: 4
   :lines: 24-48
 
 As a rule of thumb:
@@ -91,33 +96,27 @@
 
 .. literalinclude:: ../examples/scans.py
   :language: python
-  :lines: 23-33
+  :dedent: 4
+  :lines: 20-29
 
-:py:meth:`~pyignite.cache.Cache.scan` returns a generator, that yields
+:py:meth:`~pyignite.cache.Cache.scan` returns a cursor, that yields
 two-tuples of key and value. You can iterate through the generated pairs
 in a safe manner:
 
 .. literalinclude:: ../examples/scans.py
   :language: python
-  :lines: 34-41
+  :dedent: 4
+  :lines: 31-39
 
-Or, alternatively, you can convert the generator to dictionary in one go:
+Or, alternatively, you can convert the cursor to dictionary in one go:
 
 .. literalinclude:: ../examples/scans.py
   :language: python
-  :lines: 44-52
+  :dedent: 4
+  :lines: 41-50
 
 But be cautious: if the cache contains a large set of data, the dictionary
-may eat too much memory!
-
-Do cleanup
-==========
-
-Destroy created cache and close connection.
-
-.. literalinclude:: ../examples/scans.py
-  :language: python
-  :lines: 54-55
+may consume too much memory!
 
 .. _sql_examples:
 
@@ -132,7 +131,7 @@
 
 .. literalinclude:: ../examples/get_and_put_complex.py
   :language: python
-  :lines: 19-21
+  :lines: 19
 
 Map
 ===
@@ -148,7 +147,8 @@
 
 .. literalinclude:: ../examples/get_and_put_complex.py
   :language: python
-  :lines: 29-41
+  :dedent: 4
+  :lines: 26-38
 
 Collection
 ==========
@@ -164,7 +164,8 @@
 
 .. literalinclude:: ../examples/get_and_put_complex.py
   :language: python
-  :lines: 43-57
+  :dedent: 4
+  :lines: 40-54
 
 Object array
 ============
@@ -175,7 +176,8 @@
 
 .. literalinclude:: ../examples/get_and_put_complex.py
   :language: python
-  :lines: 59-68
+  :dedent: 4
+  :lines: 56-65
 
 SQL
 ---
@@ -198,19 +200,34 @@
 
 .. literalinclude:: ../examples/sql.py
   :language: python
-  :lines: 25-42, 51-59, 67-74, 199-204
+  :lines: 25-42, 51-59, 67-74
+
+.. literalinclude:: ../examples/sql.py
+  :language: python
+  :dedent: 4
+  :lines: 199-204
 
 Create indexes.
 
 .. literalinclude:: ../examples/sql.py
   :language: python
-  :lines: 60-62, 75-77, 207-208
+  :lines: 60-62, 75-77
+
+.. literalinclude:: ../examples/sql.py
+  :language: python
+  :dedent: 4
+  :lines: 207-208
 
 Fill tables with data.
 
 .. literalinclude:: ../examples/sql.py
   :language: python
-  :lines: 43-50, 63-66, 78-81, 211-218
+  :lines: 43-50, 63-66, 78-81
+
+.. literalinclude:: ../examples/sql.py
+  :language: python
+  :dedent: 4
+  :lines: 211-218
 
 Data samples are taken from `PyIgnite GitHub repository`_.
 
@@ -221,6 +238,7 @@
 
 .. literalinclude:: ../examples/sql.py
   :language: python
+  :dedent: 4
   :lines: 24, 221-238
 
 The :py:meth:`~pyignite.client.Client.sql` method returns a generator,
@@ -236,20 +254,27 @@
 
 .. literalinclude:: ../examples/sql.py
   :language: python
-  :lines: 241-269
+  :dedent: 4
+  :lines: 241-266
 
 Display all the information about a given city
 ==============================================
 
 .. literalinclude:: ../examples/sql.py
   :language: python
-  :lines: 272-290
+  :dedent: 4
+  :lines: 268-283
 
 Finally, delete the tables used in this example with the following queries:
 
 .. literalinclude:: ../examples/sql.py
   :language: python
-  :lines: 82-83, 293-298
+  :lines: 82-83
+
+.. literalinclude:: ../examples/sql.py
+  :language: python
+  :dedent: 4
+  :lines: 285-291
 
 .. _complex_object_usage:
 
@@ -291,7 +316,8 @@
 
 .. literalinclude:: ../examples/binary_basics.py
   :language: python
-  :lines: 18-20, 30-34, 39-42, 48-49
+  :dedent: 4
+  :lines: 32-34, 39-42, 48-49
 
 Here you can see how :class:`~pyignite.binary.GenericObjectMeta` uses
 `attrs`_ package internally for creating nice `__init__()` and `__repr__()`
@@ -317,14 +343,15 @@
 
 .. literalinclude:: ../examples/binary_basics.py
   :language: python
-  :lines: 53, 34-37
+  :dedent: 4
+  :lines: 52, 33-37
 
 :class:`~pyignite.binary.GenericObjectMeta` can also be used directly
 for creating custom classes:
 
 .. literalinclude:: ../examples/binary_basics.py
   :language: python
-  :lines: 22-27
+  :lines: 18-27
 
 Note how the `Person` class is defined. `schema` is a
 :class:`~pyignite.binary.GenericObjectMeta` metaclass parameter.
@@ -343,7 +370,8 @@
 
 .. literalinclude:: ../examples/binary_basics.py
   :language: python
-  :lines: 51
+  :dedent: 4
+  :lines: 50
 
 Now, when we dealt with the basics of `pyignite` implementation of Complex
 Objects, let us move on to more elaborate examples.
@@ -364,6 +392,7 @@
 
 .. literalinclude:: ../examples/read_binary.py
   :language: python
+  :dedent: 4
   :lines: 222-229
 
 We can see that Ignite created a cache for each of our tables. The caches are
@@ -374,6 +403,7 @@
 
 .. literalinclude:: ../examples/read_binary.py
   :language: python
+  :dedent: 4
   :lines: 231-251
 
 The values of `value_type_name` and `key_type_name` are names of the binary
@@ -386,6 +416,7 @@
 
 .. literalinclude:: ../examples/read_binary.py
   :language: python
+  :dedent: 4
   :lines: 253-267
 
 What we see is a tuple of key and value, extracted from the cache. Both key
@@ -421,37 +452,37 @@
 
 .. literalinclude:: ../examples/create_binary.py
   :language: python
-  :lines: 22-63
+  :dedent: 4
+  :lines: 24-63
 
 2. Define Complex object data class.
 
 .. literalinclude:: ../examples/create_binary.py
   :language: python
-  :lines: 66-76
+  :dedent: 4
+  :lines: 64-75
 
 3. Insert row.
 
 .. literalinclude:: ../examples/create_binary.py
   :language: python
-  :lines: 79-83
+  :dedent: 4
+  :lines: 76-80
 
 Now let us make sure that our cache really can be used with SQL functions.
 
 .. literalinclude:: ../examples/create_binary.py
   :language: python
-  :lines: 85-93
+  :dedent: 4
+  :lines: 82-87
 
 Note, however, that the cache we create can not be dropped with DDL command.
-
-.. literalinclude:: ../examples/create_binary.py
-  :language: python
-  :lines: 95-100
-
 It should be deleted as any other key-value cache.
 
 .. literalinclude:: ../examples/create_binary.py
   :language: python
-  :lines: 102
+  :dedent: 4
+  :lines: 89-96
 
 Migrate
 =======
@@ -470,7 +501,8 @@
 
 .. literalinclude:: ../examples/migrate_binary.py
   :language: python
-  :lines: 108-111
+  :dedent: 4
+  :lines: 111
 
 If you do not store the schema of the Complex object in code, you can obtain
 it as a dataclass property with
@@ -478,14 +510,15 @@
 
 .. literalinclude:: ../examples/migrate_binary.py
   :language: python
-  :lines: 116-123
+  :dedent: 4
+  :lines: 116-120
 
 Let us modify the schema and create a new Complex object class with an updated
 schema.
 
 .. literalinclude:: ../examples/migrate_binary.py
   :language: python
-  :lines: 125-138
+  :lines: 122-138
 
 Now migrate the data from the old schema to the new one.
 
@@ -525,21 +558,16 @@
 
 .. literalinclude:: ../examples/failover.py
   :language: python
-  :lines: 16-51
+  :lines: 16-53
 
 Then try shutting down and restarting nodes, and see what happens.
 
 .. literalinclude:: ../examples/failover.py
   :language: python
-  :lines: 53-65
+  :lines: 55-67
 
 Client reconnection do not require an explicit user action, like calling
 a special method or resetting a parameter.
-
-.. literalinclude:: ../examples/failover.py
-  :language: python
-  :lines: 48
-
 It means that instead of checking the connection status it is better for
 `pyignite` user to just try the supposed data operations and catch
 the resulting exception.
@@ -651,10 +679,12 @@
     # pyignite.exceptions.HandshakeError: Handshake error: Unauthenticated sessions are prohibited.
 
 .. _get_and_put.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/get_and_put.py
+.. _async_key_value.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/async_key_value.py
 .. _type_hints.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/type_hints.py
 .. _failover.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/failover.py
 .. _scans.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/scans.py
 .. _sql.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/sql.py
+.. _async_sql.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/async_sql.py
 .. _binary_basics.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/binary_basics.py
 .. _read_binary.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/read_binary.py
 .. _create_binary.py: https://github.com/apache/ignite-python-thin-client/blob/master/examples/create_binary.py
diff --git a/docs/index.rst b/docs/index.rst
index 35bd18c..7c28b6c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -23,6 +23,7 @@
    readme
    modules
    examples
+   async_examples
 
 
 Indices and tables
diff --git a/docs/modules.rst b/docs/modules.rst
index cabc915..0cce570 100644
--- a/docs/modules.rst
+++ b/docs/modules.rst
@@ -21,11 +21,13 @@
 of `pyignite`, intended for end users.
 
 .. toctree::
-   :maxdepth: 1
-   :caption: Modules:
+    :maxdepth: 1
+    :caption: Modules:
 
-   Client <source/pyignite.client>
-   Cache <source/pyignite.cache>
-   datatypes/parsers
-   datatypes/cache_props
-   Exceptions <source/pyignite.exceptions>
+    Client <source/pyignite.client>
+    AioClient <source/pyignite.aio_client>
+    Cache <source/pyignite.cache>
+    AioCache <source/pyignite.aio_cache>
+    datatypes/parsers
+    datatypes/cache_props
+    Exceptions <source/pyignite.exceptions>
diff --git a/docs/readme.rst b/docs/readme.rst
index 81298ae..807865a 100644
--- a/docs/readme.rst
+++ b/docs/readme.rst
@@ -35,9 +35,9 @@
 Prerequisites
 -------------
 
-- *Python 3.4* or above (3.6 is tested),
+- *Python 3.6* or above (3.6, 3.7, 3.8 and 3.9 are tested),
 - Access to *Apache Ignite* node, local or remote. The current thin client
-  version was tested on *Apache Ignite 2.7.0* (binary client protocol 1.2.0).
+  version was tested on *Apache Ignite 2.10.0* (binary client protocol 1.7.0).
 
 Installation
 ------------
@@ -59,8 +59,7 @@
 
 ::
 
-$ git clone git@github.com:apache/ignite.git
-$ cd ignite/modules/platforms/python
+$ git clone git@github.com:apache/ignite-python-thin-client.git
 $ pip install -e .
 
 This will install the repository version of `pyignite` into your environment
@@ -74,13 +73,26 @@
 
 $ pip install -r requirements/<your task>.txt
 
+
+For development, it is recommended to install `tests` requirements
+
+::
+
+$ pip install -r requirements/tests.txt
+
+For checking codestyle run:
+
+::
+
+$ flake8
+
 You may also want to consult the `setuptools`_ manual about using `setup.py`.
 
 Examples
 --------
 
 Some examples of using pyignite are provided in
-`ignite/modules/platforms/python/examples` folder. They are extensively
+`examples` folder. They are extensively
 commented in the :ref:`examples_of_usage` section of the documentation.
 
 This code implies that it is run in the environment with `pyignite` package
@@ -93,62 +105,26 @@
 Testing
 -------
 
-Create and activate virtualenv_ environment. Run
+Create and activate virtualenv_ environment.
+
+Install a binary release of Ignite with `log4j2` enabled and set `IGNITE_HOME` environment variable.
 
 ::
 
-$ cd ignite/modules/platforms/python
-$ python ./setup.py pytest
+$ cd <ignite_binary_release>
+$ export IGNITE_HOME=$(pwd)
+$ cp -r $IGNITE_HOME/libs/optional/ignite-log4j2 $IGNITE_HOME/libs/
 
-This does not require `pytest` and other test dependencies to be installed
-in your environment.
 
-Some or all tests require Apache Ignite node running on localhost:10800.
-To override the default parameters, use command line options
-``--ignite-host`` and ``--ignite-port``:
+Run
 
 ::
 
-$ python ./setup.py pytest --addopts "--ignite-host=example.com --ignite-port=19840"
-
-You can use each of these two options multiple times. All combinations
-of given host and port will be tested.
-
-You can also test client against a server with SSL-encrypted connection.
-SSL-related `pytest` parameters are:
-
-``--use-ssl`` − use SSL encryption,
-
-``--ssl-certfile`` − a path to ssl certificate file to identify local party,
-
-``--ssl-ca-certfile`` − a path to a trusted certificate or a certificate chain,
-
-``--ssl-cert-reqs`` − determines how the remote side certificate is treated:
-
-- ``NONE`` (ignore, default),
-- ``OPTIONAL`` (validate, if provided),
-- ``REQUIRED`` (valid remote certificate is required),
-
-``--ssl-ciphers`` − ciphers to use,
-
-``--ssl-version`` − SSL version:
-
-- ``TLSV1_1`` (default),
-- ``TLSV1_2``.
+$ pip install -e .
+$ pytest
 
 Other `pytest` parameters:
 
-``--timeout`` − timeout (in seconds) for each socket operation, including
-`connect`. Accepts integer or float value. Default is None (blocking mode),
-
-``--partition-aware`` − experimental; off by default; turns on the partition
-awareness: a way for the thin client to calculate a data placement for the
-given key.
-
-``--username`` and ``--password`` − credentials to authenticate to Ignite
-cluster. Used in conjunction with `authenticationEnabled` property in cluster
-configuration.
-
 ``--examples`` − run the examples as one test. If you wish to run *only*
 the examples, supply also the name of the test function to `pytest` launcher:
 
@@ -167,25 +143,33 @@
 by user or depend on special configuration of the Ignite cluster, they
 can not be automated.
 
+Using tox
+"""""""""
+For automate running tests against different python version, it is recommended to use tox_
+
+::
+
+$ pip install tox
+$ tox
+
+
 Documentation
 -------------
 To recompile this documentation, do this from your virtualenv_ environment:
 
 ::
 
-$ cd ignite/modules/platforms/python
 $ pip install -r requirements/docs.txt
 $ cd docs
 $ make html
 
-Then open `ignite/modules/platforms/python/docs/generated/html/index.html`_
+Then open `docs/generated/html/index.html`_
 in your browser.
 
 If you feel that old version is stuck, do
 
 ::
 
-$ cd ignite/modules/platforms/python/docs
 $ make clean
 $ sphinx-apidoc -feM -o source/ ../ ../setup.py
 $ make html
@@ -201,6 +185,7 @@
 .. _binary client protocol: https://apacheignite.readme.io/docs/binary-client-protocol
 .. _Apache License v2: http://www.apache.org/licenses/LICENSE-2.0
 .. _virtualenv: https://virtualenv.pypa.io/
+.. _tox: https://tox.readthedocs.io/en/latest/
 .. _setuptools: https://setuptools.readthedocs.io/
-.. _ignite/modules/platforms/python/docs/generated/html/index.html: .
+.. _docs/generated/html/index.html: .
 .. _editable installs: https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs
diff --git a/docs/source/modules.rst b/docs/source/modules.rst
index c125dd3..189a011 100644
--- a/docs/source/modules.rst
+++ b/docs/source/modules.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 python
 ======
 
diff --git a/docs/source/pyignite.aio_cache.rst b/docs/source/pyignite.aio_cache.rst
new file mode 100644
index 0000000..b62a33a
--- /dev/null
+++ b/docs/source/pyignite.aio_cache.rst
@@ -0,0 +1,22 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+pyignite.aio_cache module
+=========================
+
+.. automodule:: pyignite.aio_cache
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/docs/source/pyignite.aio_client.rst b/docs/source/pyignite.aio_client.rst
new file mode 100644
index 0000000..922c559
--- /dev/null
+++ b/docs/source/pyignite.aio_client.rst
@@ -0,0 +1,22 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+pyignite.aio_client module
+==========================
+
+.. automodule:: pyignite.aio_client
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/docs/source/pyignite.api.binary.rst b/docs/source/pyignite.api.binary.rst
deleted file mode 100644
index 49f1c86..0000000
--- a/docs/source/pyignite.api.binary.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.api.binary module
-==========================
-
-.. automodule:: pyignite.api.binary
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.api.cache_config.rst b/docs/source/pyignite.api.cache_config.rst
deleted file mode 100644
index 599c857..0000000
--- a/docs/source/pyignite.api.cache_config.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.api.cache\_config module
-=================================
-
-.. automodule:: pyignite.api.cache_config
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.api.key_value.rst b/docs/source/pyignite.api.key_value.rst
deleted file mode 100644
index 52d6c3f..0000000
--- a/docs/source/pyignite.api.key_value.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.api.key\_value module
-==============================
-
-.. automodule:: pyignite.api.key_value
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.api.result.rst b/docs/source/pyignite.api.result.rst
deleted file mode 100644
index 21398e3..0000000
--- a/docs/source/pyignite.api.result.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.api.result module
-==========================
-
-.. automodule:: pyignite.api.result
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.api.rst b/docs/source/pyignite.api.rst
deleted file mode 100644
index e18d4a3..0000000
--- a/docs/source/pyignite.api.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-pyignite.api package
-====================
-
-.. automodule:: pyignite.api
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-Submodules
-----------
-
-.. toctree::
-
-   pyignite.api.binary
-   pyignite.api.cache_config
-   pyignite.api.key_value
-   pyignite.api.result
-   pyignite.api.sql
-
diff --git a/docs/source/pyignite.api.sql.rst b/docs/source/pyignite.api.sql.rst
deleted file mode 100644
index 84479ad..0000000
--- a/docs/source/pyignite.api.sql.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.api.sql module
-=======================
-
-.. automodule:: pyignite.api.sql
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.binary.rst b/docs/source/pyignite.binary.rst
index 6b21582..eeab940 100644
--- a/docs/source/pyignite.binary.rst
+++ b/docs/source/pyignite.binary.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.binary module
 ======================
 
diff --git a/docs/source/pyignite.cache.rst b/docs/source/pyignite.cache.rst
index e6e83c5..f4099de 100644
--- a/docs/source/pyignite.cache.rst
+++ b/docs/source/pyignite.cache.rst
@@ -1,7 +1,22 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.cache module
 =====================
 
 .. automodule:: pyignite.cache
     :members:
     :undoc-members:
-    :show-inheritance:
+    :inherited-members:
diff --git a/docs/source/pyignite.client.rst b/docs/source/pyignite.client.rst
index fef316b..e978dc1 100644
--- a/docs/source/pyignite.client.rst
+++ b/docs/source/pyignite.client.rst
@@ -1,7 +1,22 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.client module
 ======================
 
 .. automodule:: pyignite.client
     :members:
     :undoc-members:
-    :show-inheritance:
+    :inherited-members:
diff --git a/docs/source/pyignite.connection.handshake.rst b/docs/source/pyignite.connection.handshake.rst
deleted file mode 100644
index 28e83df..0000000
--- a/docs/source/pyignite.connection.handshake.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.connection.handshake module
-====================================
-
-.. automodule:: pyignite.connection.handshake
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.connection.rst b/docs/source/pyignite.connection.rst
index f1acd2b..90c59db 100644
--- a/docs/source/pyignite.connection.rst
+++ b/docs/source/pyignite.connection.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.connection package
 ===========================
 
@@ -5,12 +20,3 @@
     :members:
     :undoc-members:
     :show-inheritance:
-
-Submodules
-----------
-
-.. toctree::
-
-   pyignite.connection.handshake
-   pyignite.connection.ssl
-
diff --git a/docs/source/pyignite.connection.ssl.rst b/docs/source/pyignite.connection.ssl.rst
deleted file mode 100644
index 8eebf43..0000000
--- a/docs/source/pyignite.connection.ssl.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.connection.ssl module
-==============================
-
-.. automodule:: pyignite.connection.ssl
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.constants.rst b/docs/source/pyignite.constants.rst
deleted file mode 100644
index f71e4f1..0000000
--- a/docs/source/pyignite.constants.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.constants module
-=========================
-
-.. automodule:: pyignite.constants
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.cursors.rst b/docs/source/pyignite.cursors.rst
new file mode 100644
index 0000000..6415a16
--- /dev/null
+++ b/docs/source/pyignite.cursors.rst
@@ -0,0 +1,22 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+pyignite.cursors module
+=======================
+
+.. automodule:: pyignite.cursors
+    :members:
+    :undoc-members:
+    :inherited-members:
diff --git a/docs/source/pyignite.datatypes.base.rst b/docs/source/pyignite.datatypes.base.rst
index 849a028..c482904 100644
--- a/docs/source/pyignite.datatypes.base.rst
+++ b/docs/source/pyignite.datatypes.base.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.base module
 ==============================
 
diff --git a/docs/source/pyignite.datatypes.binary.rst b/docs/source/pyignite.datatypes.binary.rst
index 0d175de..37de8b8 100644
--- a/docs/source/pyignite.datatypes.binary.rst
+++ b/docs/source/pyignite.datatypes.binary.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.binary module
 ================================
 
diff --git a/docs/source/pyignite.datatypes.cache_config.rst b/docs/source/pyignite.datatypes.cache_config.rst
index 3d5eaeb..4b63637 100644
--- a/docs/source/pyignite.datatypes.cache_config.rst
+++ b/docs/source/pyignite.datatypes.cache_config.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.cache\_config module
 =======================================
 
diff --git a/docs/source/pyignite.datatypes.cache_properties.rst b/docs/source/pyignite.datatypes.cache_properties.rst
index 57f0e9f..d626366 100644
--- a/docs/source/pyignite.datatypes.cache_properties.rst
+++ b/docs/source/pyignite.datatypes.cache_properties.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.cache\_properties module
 ===========================================
 
diff --git a/docs/source/pyignite.datatypes.complex.rst b/docs/source/pyignite.datatypes.complex.rst
index 1e3f21e..83ecacc 100644
--- a/docs/source/pyignite.datatypes.complex.rst
+++ b/docs/source/pyignite.datatypes.complex.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.complex module
 =================================
 
diff --git a/docs/source/pyignite.datatypes.internal.rst b/docs/source/pyignite.datatypes.internal.rst
index 5dc5535..a3e5dcc 100644
--- a/docs/source/pyignite.datatypes.internal.rst
+++ b/docs/source/pyignite.datatypes.internal.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.internal module
 ==================================
 
diff --git a/docs/source/pyignite.datatypes.key_value.rst b/docs/source/pyignite.datatypes.key_value.rst
index 0b3aa88..46d83dd 100644
--- a/docs/source/pyignite.datatypes.key_value.rst
+++ b/docs/source/pyignite.datatypes.key_value.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.key\_value module
 ====================================
 
diff --git a/docs/source/pyignite.datatypes.null_object.rst b/docs/source/pyignite.datatypes.null_object.rst
index 05f22b1..5d6381f 100644
--- a/docs/source/pyignite.datatypes.null_object.rst
+++ b/docs/source/pyignite.datatypes.null_object.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.null\_object module
 ======================================
 
diff --git a/docs/source/pyignite.datatypes.primitive.rst b/docs/source/pyignite.datatypes.primitive.rst
index 8a53604..3fa2797 100644
--- a/docs/source/pyignite.datatypes.primitive.rst
+++ b/docs/source/pyignite.datatypes.primitive.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.primitive module
 ===================================
 
diff --git a/docs/source/pyignite.datatypes.primitive_arrays.rst b/docs/source/pyignite.datatypes.primitive_arrays.rst
index b4b94bf..d261235 100644
--- a/docs/source/pyignite.datatypes.primitive_arrays.rst
+++ b/docs/source/pyignite.datatypes.primitive_arrays.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.primitive\_arrays module
 ===========================================
 
diff --git a/docs/source/pyignite.datatypes.primitive_objects.rst b/docs/source/pyignite.datatypes.primitive_objects.rst
index a74db38..e737f3c 100644
--- a/docs/source/pyignite.datatypes.primitive_objects.rst
+++ b/docs/source/pyignite.datatypes.primitive_objects.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.primitive\_objects module
 ============================================
 
diff --git a/docs/source/pyignite.datatypes.prop_codes.rst b/docs/source/pyignite.datatypes.prop_codes.rst
deleted file mode 100644
index d23596b..0000000
--- a/docs/source/pyignite.datatypes.prop_codes.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.datatypes.prop\_codes module
-=====================================
-
-.. automodule:: pyignite.datatypes.prop_codes
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.datatypes.rst b/docs/source/pyignite.datatypes.rst
index d72f844..269d500 100644
--- a/docs/source/pyignite.datatypes.rst
+++ b/docs/source/pyignite.datatypes.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes package
 ==========================
 
@@ -22,8 +37,6 @@
    pyignite.datatypes.primitive
    pyignite.datatypes.primitive_arrays
    pyignite.datatypes.primitive_objects
-   pyignite.datatypes.prop_codes
    pyignite.datatypes.sql
    pyignite.datatypes.standard
-   pyignite.datatypes.type_codes
 
diff --git a/docs/source/pyignite.datatypes.sql.rst b/docs/source/pyignite.datatypes.sql.rst
index e20f084..8e564b8 100644
--- a/docs/source/pyignite.datatypes.sql.rst
+++ b/docs/source/pyignite.datatypes.sql.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.sql module
 =============================
 
diff --git a/docs/source/pyignite.datatypes.standard.rst b/docs/source/pyignite.datatypes.standard.rst
index e46d339..f181450 100644
--- a/docs/source/pyignite.datatypes.standard.rst
+++ b/docs/source/pyignite.datatypes.standard.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.datatypes.standard module
 ==================================
 
diff --git a/docs/source/pyignite.datatypes.type_codes.rst b/docs/source/pyignite.datatypes.type_codes.rst
deleted file mode 100644
index 47baa4b..0000000
--- a/docs/source/pyignite.datatypes.type_codes.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.datatypes.type\_codes module
-=====================================
-
-.. automodule:: pyignite.datatypes.type_codes
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.exceptions.rst b/docs/source/pyignite.exceptions.rst
index dd24687..563ea90 100644
--- a/docs/source/pyignite.exceptions.rst
+++ b/docs/source/pyignite.exceptions.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite.exceptions module
 ==========================
 
diff --git a/docs/source/pyignite.queries.op_codes.rst b/docs/source/pyignite.queries.op_codes.rst
deleted file mode 100644
index bc556ec..0000000
--- a/docs/source/pyignite.queries.op_codes.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.queries.op\_codes module
-=================================
-
-.. automodule:: pyignite.queries.op_codes
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/pyignite.queries.rst b/docs/source/pyignite.queries.rst
deleted file mode 100644
index 6dd81a2..0000000
--- a/docs/source/pyignite.queries.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-pyignite.queries package
-========================
-
-.. automodule:: pyignite.queries
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-Submodules
-----------
-
-.. toctree::
-
-   pyignite.queries.op_codes
-
diff --git a/docs/source/pyignite.rst b/docs/source/pyignite.rst
index 947cab2..85e31a8 100644
--- a/docs/source/pyignite.rst
+++ b/docs/source/pyignite.rst
@@ -1,3 +1,18 @@
+..  Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+..      http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
 pyignite package
 ================
 
@@ -11,20 +26,20 @@
 
 .. toctree::
 
-    pyignite.api
-    pyignite.connection
     pyignite.datatypes
-    pyignite.queries
+    pyignite.connection
 
 Submodules
 ----------
 
 .. toctree::
 
-   pyignite.binary
-   pyignite.cache
-   pyignite.client
-   pyignite.constants
-   pyignite.exceptions
-   pyignite.utils
+    pyignite.binary
+    pyignite.cache
+    pyignite.aio_cache
+    pyignite.client
+    pyignite.aio_client
+    pyignite.constants
+    pyignite.cursors
+    pyignite.exceptions
 
diff --git a/docs/source/pyignite.utils.rst b/docs/source/pyignite.utils.rst
deleted file mode 100644
index 5ee42ab..0000000
--- a/docs/source/pyignite.utils.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pyignite.utils module
-=====================
-
-.. automodule:: pyignite.utils
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/examples/async_key_value.py b/examples/async_key_value.py
new file mode 100644
index 0000000..76dac34
--- /dev/null
+++ b/examples/async_key_value.py
@@ -0,0 +1,56 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import asyncio
+
+from pyignite import AioClient
+
+
+async def main():
+    # Create client and connect.
+    client = AioClient()
+    async with client.connect('127.0.0.1', 10800):
+        # Create cache
+        cache = await client.get_or_create_cache('test_async_cache')
+
+        # Load data concurrently.
+        await asyncio.gather(
+            *[cache.put(f'key_{i}', f'value_{i}') for i in range(0, 20)]
+        )
+
+        # Key-value queries.
+        print(await cache.get('key_10'))
+        print(await cache.get_all([f'key_{i}' for i in range(0, 10)]))
+        # value_10
+        # {'key_3': 'value_3', 'key_2': 'value_2', 'key_1': 'value_1','....}
+
+        # Scan query.
+        async with cache.scan() as cursor:
+            async for k, v in cursor:
+                print(f'key = {k}, value = {v}')
+        # key = key_42, value = value_42
+        # key = key_43, value = value_43
+        # key = key_40, value = value_40
+        # key = key_41, value = value_41
+        # key = key_37, value = value_37
+        # key = key_51, value = value_51
+        # key = key_20, value = value_20
+        # ......
+
+        # Clean up.
+        await cache.destroy()
+
+loop = asyncio.get_event_loop()
+loop.run_until_complete(main())
diff --git a/examples/async_sql.py b/examples/async_sql.py
new file mode 100644
index 0000000..ffd2939
--- /dev/null
+++ b/examples/async_sql.py
@@ -0,0 +1,301 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import asyncio
+from decimal import Decimal
+
+from pyignite import AioClient
+
+
+COUNTRY_TABLE_NAME = 'Country'
+CITY_TABLE_NAME = 'City'
+LANGUAGE_TABLE_NAME = 'CountryLanguage'
+
+COUNTRY_CREATE_TABLE_QUERY = '''CREATE TABLE Country (
+    Code CHAR(3) PRIMARY KEY,
+    Name CHAR(52),
+    Continent CHAR(50),
+    Region CHAR(26),
+    SurfaceArea DECIMAL(10,2),
+    IndepYear SMALLINT(6),
+    Population INT(11),
+    LifeExpectancy DECIMAL(3,1),
+    GNP DECIMAL(10,2),
+    GNPOld DECIMAL(10,2),
+    LocalName CHAR(45),
+    GovernmentForm CHAR(45),
+    HeadOfState CHAR(60),
+    Capital INT(11),
+    Code2 CHAR(2)
+)'''
+
+COUNTRY_INSERT_QUERY = '''INSERT INTO Country(
+    Code, Name, Continent, Region,
+    SurfaceArea, IndepYear, Population,
+    LifeExpectancy, GNP, GNPOld,
+    LocalName, GovernmentForm, HeadOfState,
+    Capital, Code2
+) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'''
+
+CITY_CREATE_TABLE_QUERY = '''CREATE TABLE City (
+    ID INT(11),
+    Name CHAR(35),
+    CountryCode CHAR(3),
+    District CHAR(20),
+    Population INT(11),
+    PRIMARY KEY (ID, CountryCode)
+) WITH "affinityKey=CountryCode"'''
+
+CITY_CREATE_INDEX = '''
+CREATE INDEX idx_country_code ON city (CountryCode)'''
+
+CITY_INSERT_QUERY = '''INSERT INTO City(
+    ID, Name, CountryCode, District, Population
+) VALUES (?, ?, ?, ?, ?)'''
+
+LANGUAGE_CREATE_TABLE_QUERY = '''CREATE TABLE CountryLanguage (
+    CountryCode CHAR(3),
+    Language CHAR(30),
+    IsOfficial BOOLEAN,
+    Percentage DECIMAL(4,1),
+    PRIMARY KEY (CountryCode, Language)
+) WITH "affinityKey=CountryCode"'''
+
+LANGUAGE_CREATE_INDEX = '''
+CREATE INDEX idx_lang_country_code ON CountryLanguage (CountryCode)'''
+
+LANGUAGE_INSERT_QUERY = '''INSERT INTO CountryLanguage(
+    CountryCode, Language, IsOfficial, Percentage
+) VALUES (?, ?, ?, ?)'''
+
+DROP_TABLE_QUERY = '''DROP TABLE {} IF EXISTS'''
+
+COUNTRY_DATA = [
+    [
+        'USA', 'United States', 'North America', 'North America',
+        Decimal('9363520.00'), 1776, 278357000,
+        Decimal('77.1'), Decimal('8510700.00'), Decimal('8110900.00'),
+        'United States', 'Federal Republic', 'George W. Bush',
+        3813, 'US',
+    ],
+    [
+        'IND', 'India', 'Asia', 'Southern and Central Asia',
+        Decimal('3287263.00'), 1947, 1013662000,
+        Decimal('62.5'), Decimal('447114.00'), Decimal('430572.00'),
+        'Bharat/India', 'Federal Republic', 'Kocheril Raman Narayanan',
+        1109, 'IN',
+    ],
+    [
+        'CHN', 'China', 'Asia', 'Eastern Asia',
+        Decimal('9572900.00'), -1523, 1277558000,
+        Decimal('71.4'), Decimal('982268.00'), Decimal('917719.00'),
+        'Zhongquo', 'PeoplesRepublic', 'Jiang Zemin',
+        1891, 'CN',
+    ],
+]
+
+CITY_DATA = [
+    [3793, 'New York', 'USA', 'New York', 8008278],
+    [3794, 'Los Angeles', 'USA', 'California', 3694820],
+    [3795, 'Chicago', 'USA', 'Illinois', 2896016],
+    [3796, 'Houston', 'USA', 'Texas', 1953631],
+    [3797, 'Philadelphia', 'USA', 'Pennsylvania', 1517550],
+    [3798, 'Phoenix', 'USA', 'Arizona', 1321045],
+    [3799, 'San Diego', 'USA', 'California', 1223400],
+    [3800, 'Dallas', 'USA', 'Texas', 1188580],
+    [3801, 'San Antonio', 'USA', 'Texas', 1144646],
+    [3802, 'Detroit', 'USA', 'Michigan', 951270],
+    [3803, 'San Jose', 'USA', 'California', 894943],
+    [3804, 'Indianapolis', 'USA', 'Indiana', 791926],
+    [3805, 'San Francisco', 'USA', 'California', 776733],
+    [1024, 'Mumbai (Bombay)', 'IND', 'Maharashtra', 10500000],
+    [1025, 'Delhi', 'IND', 'Delhi', 7206704],
+    [1026, 'Calcutta [Kolkata]', 'IND', 'West Bengali', 4399819],
+    [1027, 'Chennai (Madras)', 'IND', 'Tamil Nadu', 3841396],
+    [1028, 'Hyderabad', 'IND', 'Andhra Pradesh', 2964638],
+    [1029, 'Ahmedabad', 'IND', 'Gujarat', 2876710],
+    [1030, 'Bangalore', 'IND', 'Karnataka', 2660088],
+    [1031, 'Kanpur', 'IND', 'Uttar Pradesh', 1874409],
+    [1032, 'Nagpur', 'IND', 'Maharashtra', 1624752],
+    [1033, 'Lucknow', 'IND', 'Uttar Pradesh', 1619115],
+    [1034, 'Pune', 'IND', 'Maharashtra', 1566651],
+    [1035, 'Surat', 'IND', 'Gujarat', 1498817],
+    [1036, 'Jaipur', 'IND', 'Rajasthan', 1458483],
+    [1890, 'Shanghai', 'CHN', 'Shanghai', 9696300],
+    [1891, 'Peking', 'CHN', 'Peking', 7472000],
+    [1892, 'Chongqing', 'CHN', 'Chongqing', 6351600],
+    [1893, 'Tianjin', 'CHN', 'Tianjin', 5286800],
+    [1894, 'Wuhan', 'CHN', 'Hubei', 4344600],
+    [1895, 'Harbin', 'CHN', 'Heilongjiang', 4289800],
+    [1896, 'Shenyang', 'CHN', 'Liaoning', 4265200],
+    [1897, 'Kanton [Guangzhou]', 'CHN', 'Guangdong', 4256300],
+    [1898, 'Chengdu', 'CHN', 'Sichuan', 3361500],
+    [1899, 'Nanking [Nanjing]', 'CHN', 'Jiangsu', 2870300],
+    [1900, 'Changchun', 'CHN', 'Jilin', 2812000],
+    [1901, 'Xi´an', 'CHN', 'Shaanxi', 2761400],
+    [1902, 'Dalian', 'CHN', 'Liaoning', 2697000],
+    [1903, 'Qingdao', 'CHN', 'Shandong', 2596000],
+    [1904, 'Jinan', 'CHN', 'Shandong', 2278100],
+    [1905, 'Hangzhou', 'CHN', 'Zhejiang', 2190500],
+    [1906, 'Zhengzhou', 'CHN', 'Henan', 2107200],
+]
+
+LANGUAGE_DATA = [
+    ['USA', 'Chinese', False, Decimal('0.6')],
+    ['USA', 'English', True, Decimal('86.2')],
+    ['USA', 'French', False, Decimal('0.7')],
+    ['USA', 'German', False, Decimal('0.7')],
+    ['USA', 'Italian', False, Decimal('0.6')],
+    ['USA', 'Japanese', False, Decimal('0.2')],
+    ['USA', 'Korean', False, Decimal('0.3')],
+    ['USA', 'Polish', False, Decimal('0.3')],
+    ['USA', 'Portuguese', False, Decimal('0.2')],
+    ['USA', 'Spanish', False, Decimal('7.5')],
+    ['USA', 'Tagalog', False, Decimal('0.4')],
+    ['USA', 'Vietnamese', False, Decimal('0.2')],
+    ['IND', 'Asami', False, Decimal('1.5')],
+    ['IND', 'Bengali', False, Decimal('8.2')],
+    ['IND', 'Gujarati', False, Decimal('4.8')],
+    ['IND', 'Hindi', True, Decimal('39.9')],
+    ['IND', 'Kannada', False, Decimal('3.9')],
+    ['IND', 'Malajalam', False, Decimal('3.6')],
+    ['IND', 'Marathi', False, Decimal('7.4')],
+    ['IND', 'Orija', False, Decimal('3.3')],
+    ['IND', 'Punjabi', False, Decimal('2.8')],
+    ['IND', 'Tamil', False, Decimal('6.3')],
+    ['IND', 'Telugu', False, Decimal('7.8')],
+    ['IND', 'Urdu', False, Decimal('5.1')],
+    ['CHN', 'Chinese', True, Decimal('92.0')],
+    ['CHN', 'Dong', False, Decimal('0.2')],
+    ['CHN', 'Hui', False, Decimal('0.8')],
+    ['CHN', 'Mantšu', False, Decimal('0.9')],
+    ['CHN', 'Miao', False, Decimal('0.7')],
+    ['CHN', 'Mongolian', False, Decimal('0.4')],
+    ['CHN', 'Puyi', False, Decimal('0.2')],
+    ['CHN', 'Tibetan', False, Decimal('0.4')],
+    ['CHN', 'Tujia', False, Decimal('0.5')],
+    ['CHN', 'Uighur', False, Decimal('0.6')],
+    ['CHN', 'Yi', False, Decimal('0.6')],
+    ['CHN', 'Zhuang', False, Decimal('1.4')],
+]
+
+
+async def main():
+    # establish connection
+    client = AioClient()
+    async with client.connect('127.0.0.1', 10800):
+        # create tables
+        for query in [
+            COUNTRY_CREATE_TABLE_QUERY,
+            CITY_CREATE_TABLE_QUERY,
+            LANGUAGE_CREATE_TABLE_QUERY,
+        ]:
+            await client.sql(query)
+
+        # create indices
+        for query in [CITY_CREATE_INDEX, LANGUAGE_CREATE_INDEX]:
+            await client.sql(query)
+
+        # load data concurrently.
+        await asyncio.gather(*[
+            client.sql(COUNTRY_INSERT_QUERY, query_args=row) for row in COUNTRY_DATA
+        ])
+
+        await asyncio.gather(*[
+            client.sql(CITY_INSERT_QUERY, query_args=row) for row in CITY_DATA
+        ])
+
+        await asyncio.gather(*[
+            client.sql(LANGUAGE_INSERT_QUERY, query_args=row) for row in LANGUAGE_DATA
+        ])
+
+        # 10 most populated cities (with pagination)
+        MOST_POPULATED_QUERY = '''
+        SELECT name, population FROM City ORDER BY population DESC LIMIT 10'''
+
+        async with client.sql(MOST_POPULATED_QUERY) as cursor:
+            print('Most 10 populated cities:')
+
+            async for row in cursor:
+                print(row)
+        # Most 10 populated cities:
+        # ['Mumbai (Bombay)', 10500000]
+        # ['Shanghai', 9696300]
+        # ['New York', 8008278]
+        # ['Peking', 7472000]
+        # ['Delhi', 7206704]
+        # ['Chongqing', 6351600]
+        # ['Tianjin', 5286800]
+        # ['Calcutta [Kolkata]', 4399819]
+        # ['Wuhan', 4344600]
+        # ['Harbin', 4289800]
+
+        # 10 most populated cities in 3 countries (with pagination and header row)
+        MOST_POPULATED_IN_3_COUNTRIES_QUERY = '''
+        SELECT country.name as country_name, city.name as city_name, MAX(city.population) AS max_pop FROM country
+            JOIN city ON city.countrycode = country.code
+            WHERE country.code IN ('USA','IND','CHN')
+            GROUP BY country.name, city.name ORDER BY max_pop DESC LIMIT 10
+        '''
+
+        async with client.sql(MOST_POPULATED_IN_3_COUNTRIES_QUERY, include_field_names=True) as cursor:
+            print('Most 10 populated cities in USA, India and China:')
+            print(await cursor.__anext__())
+            print('----------------------------------------')
+            async for row in cursor:
+                print(row)
+        # Most 10 populated cities in USA, India and China:
+        # ['COUNTRY_NAME', 'CITY_NAME', 'MAX_POP']
+        # ----------------------------------------
+        # ['India', 'Mumbai (Bombay)', 10500000]
+        # ['China', 'Shanghai', 9696300]
+        # ['United States', 'New York', 8008278]
+        # ['China', 'Peking', 7472000]
+        # ['India', 'Delhi', 7206704]
+        # ['China', 'Chongqing', 6351600]
+        # ['China', 'Tianjin', 5286800]
+        # ['India', 'Calcutta [Kolkata]', 4399819]
+        # ['China', 'Wuhan', 4344600]
+        # ['China', 'Harbin', 4289800]
+
+        # show city info
+        CITY_INFO_QUERY = '''SELECT * FROM City WHERE id = ?'''
+
+        async with client.sql(CITY_INFO_QUERY, query_args=[3802], include_field_names=True) as cursor:
+            field_names = await cursor.__anext__()
+            field_data = await cursor.__anext__()
+
+            print('City info:')
+            for field_name, field_value in zip(field_names * len(field_data), field_data):
+                print('{}: {}'.format(field_name, field_value))
+        # City info:
+        # ID: 3802
+        # NAME: Detroit
+        # COUNTRYCODE: USA
+        # DISTRICT: Michigan
+        # POPULATION: 951270
+
+        # clean up concurrently.
+        await asyncio.gather(*[
+            client.sql(DROP_TABLE_QUERY.format(table_name)) for table_name in [
+                CITY_TABLE_NAME,
+                LANGUAGE_TABLE_NAME,
+                COUNTRY_TABLE_NAME,
+            ]
+        ])
+
+
+loop = asyncio.get_event_loop()
+loop.run_until_complete(main())
diff --git a/examples/binary_basics.py b/examples/binary_basics.py
index 96a9058..50fa933 100644
--- a/examples/binary_basics.py
+++ b/examples/binary_basics.py
@@ -16,7 +16,7 @@
 from collections import OrderedDict
 
 from pyignite import Client, GenericObjectMeta
-from pyignite.datatypes import *
+from pyignite.datatypes import String, IntObject
 
 
 class Person(metaclass=GenericObjectMeta, schema=OrderedDict([
@@ -28,26 +28,25 @@
 
 
 client = Client()
-client.connect('localhost', 10800)
+with client.connect('localhost', 10800):
+    person_cache = client.get_or_create_cache('person')
 
-person_cache = client.get_or_create_cache('person')
+    person_cache.put(
+        1, Person(first_name='Ivan', last_name='Ivanov', age=33)
+    )
 
-person_cache.put(
-    1, Person(first_name='Ivan', last_name='Ivanov', age=33)
-)
+    person = person_cache.get(1)
+    print(person.__class__.__name__)
+    # Person
 
-person = person_cache.get(1)
-print(person.__class__.__name__)
-# Person
+    print(person.__class__ is Person)
+    # True if `Person` was registered automatically (on writing)
+    # or manually (using `client.register_binary_type()` method).
+    # False otherwise
 
-print(person.__class__ is Person)
-# True if `Person` was registered automatically (on writing)
-# or manually (using `client.register_binary_type()` method).
-# False otherwise
+    print(person)
+    # Person(first_name='Ivan', last_name='Ivanov', age=33, version=1)
 
-print(person)
-# Person(first_name='Ivan', last_name='Ivanov', age=33, version=1)
+    client.register_binary_type(Person)
 
-client.register_binary_type(Person)
-
-Person = person.__class__
+    Person = person.__class__
diff --git a/examples/create_binary.py b/examples/create_binary.py
index b199527..d2c2ce4 100644
--- a/examples/create_binary.py
+++ b/examples/create_binary.py
@@ -17,87 +17,80 @@
 
 from pyignite import Client, GenericObjectMeta
 from pyignite.datatypes import DoubleObject, IntObject, String
-from pyignite.datatypes.prop_codes import *
+from pyignite.datatypes.prop_codes import PROP_NAME, PROP_SQL_SCHEMA, PROP_QUERY_ENTITIES
 
 client = Client()
-client.connect('127.0.0.1', 10800)
+with client.connect('127.0.0.1', 10800):
+    student_cache = client.create_cache({
+        PROP_NAME: 'SQL_PUBLIC_STUDENT',
+        PROP_SQL_SCHEMA: 'PUBLIC',
+        PROP_QUERY_ENTITIES: [
+            {
+                'table_name': 'Student'.upper(),
+                'key_field_name': 'SID',
+                'key_type_name': 'java.lang.Integer',
+                'field_name_aliases': [],
+                'query_fields': [
+                    {
+                        'name': 'SID',
+                        'type_name': 'java.lang.Integer',
+                        'is_key_field': True,
+                        'is_notnull_constraint_field': True,
+                    },
+                    {
+                        'name': 'NAME',
+                        'type_name': 'java.lang.String',
+                    },
+                    {
+                        'name': 'LOGIN',
+                        'type_name': 'java.lang.String',
+                    },
+                    {
+                        'name': 'AGE',
+                        'type_name': 'java.lang.Integer',
+                    },
+                    {
+                        'name': 'GPA',
+                        'type_name': 'java.math.Double',
+                    },
+                ],
+                'query_indexes': [],
+                'value_type_name': 'SQL_PUBLIC_STUDENT_TYPE',
+                'value_field_name': None,
+            },
+        ],
+    })
 
-student_cache = client.create_cache({
-    PROP_NAME: 'SQL_PUBLIC_STUDENT',
-    PROP_SQL_SCHEMA: 'PUBLIC',
-    PROP_QUERY_ENTITIES: [
-        {
-            'table_name': 'Student'.upper(),
-            'key_field_name': 'SID',
-            'key_type_name': 'java.lang.Integer',
-            'field_name_aliases': [],
-            'query_fields': [
-                {
-                    'name': 'SID',
-                    'type_name': 'java.lang.Integer',
-                    'is_key_field': True,
-                    'is_notnull_constraint_field': True,
-                },
-                {
-                    'name': 'NAME',
-                    'type_name': 'java.lang.String',
-                },
-                {
-                    'name': 'LOGIN',
-                    'type_name': 'java.lang.String',
-                },
-                {
-                    'name': 'AGE',
-                    'type_name': 'java.lang.Integer',
-                },
-                {
-                    'name': 'GPA',
-                    'type_name': 'java.math.Double',
-                },
-            ],
-            'query_indexes': [],
-            'value_type_name': 'SQL_PUBLIC_STUDENT_TYPE',
-            'value_field_name': None,
-        },
-    ],
-})
+    class Student(
+        metaclass=GenericObjectMeta,
+        type_name='SQL_PUBLIC_STUDENT_TYPE',
+        schema=OrderedDict([
+            ('NAME', String),
+            ('LOGIN', String),
+            ('AGE', IntObject),
+            ('GPA', DoubleObject),
+        ])
+    ):
+        pass
 
+    student_cache.put(
+        1,
+        Student(LOGIN='jdoe', NAME='John Doe', AGE=17, GPA=4.25),
+        key_hint=IntObject
+    )
 
-class Student(
-    metaclass=GenericObjectMeta,
-    type_name='SQL_PUBLIC_STUDENT_TYPE',
-    schema=OrderedDict([
-        ('NAME', String),
-        ('LOGIN', String),
-        ('AGE', IntObject),
-        ('GPA', DoubleObject),
-    ])
-):
-    pass
+    with client.sql(r'SELECT * FROM Student', include_field_names=True) as cursor:
+        print(next(cursor))
+        # ['SID', 'NAME', 'LOGIN', 'AGE', 'GPA']
 
+        print(*cursor)
+        # [1, 'John Doe', 'jdoe', 17, 4.25]
 
-student_cache.put(
-    1,
-    Student(LOGIN='jdoe', NAME='John Doe', AGE=17, GPA=4.25),
-    key_hint=IntObject
-)
+    # DROP_QUERY = 'DROP TABLE Student'
+    # client.sql(DROP_QUERY)
+    #
+    # pyignite.exceptions.SQLError: class org.apache.ignite.IgniteCheckedException:
+    # Only cache created with CREATE TABLE may be removed with DROP TABLE
+    # [cacheName=SQL_PUBLIC_STUDENT]
 
-result = client.sql(
-    r'SELECT * FROM Student',
-    include_field_names=True
-)
-print(next(result))
-# ['SID', 'NAME', 'LOGIN', 'AGE', 'GPA']
-
-print(*result)
-# [1, 'John Doe', 'jdoe', 17, 4.25]
-
-# DROP_QUERY = 'DROP TABLE Student'
-# client.sql(DROP_QUERY)
-#
-# pyignite.exceptions.SQLError: class org.apache.ignite.IgniteCheckedException:
-# Only cache created with CREATE TABLE may be removed with DROP TABLE
-# [cacheName=SQL_PUBLIC_STUDENT]
-
-student_cache.destroy()
-client.close()
+    student_cache.destroy()
diff --git a/examples/failover.py b/examples/failover.py
index 7911ce0..21ab547 100644
--- a/examples/failover.py
+++ b/examples/failover.py
@@ -15,7 +15,7 @@
 
 from pyignite import Client
 from pyignite.datatypes.cache_config import CacheMode
-from pyignite.datatypes.prop_codes import *
+from pyignite.datatypes.prop_codes import PROP_NAME, PROP_CACHE_MODE, PROP_BACKUPS_NUMBER
 from pyignite.exceptions import SocketError
 
 
@@ -25,30 +25,32 @@
     ('127.0.0.1', 10802),
 ]
 
-client = Client(timeout=4.0)
-client.connect(nodes)
-print('Connected')
 
-my_cache = client.get_or_create_cache({
-    PROP_NAME: 'my_cache',
-    PROP_CACHE_MODE: CacheMode.PARTITIONED,
-    PROP_BACKUPS_NUMBER: 2,
-})
-my_cache.put('test_key', 0)
-test_value = 0
+def main():
+    client = Client(timeout=4.0)
+    with client.connect(nodes):
+        print('Connected')
 
-# abstract main loop
-while True:
-    try:
-        # do the work
-        test_value = my_cache.get('test_key') or 0
-        my_cache.put('test_key', test_value + 1)
-    except (OSError, SocketError) as e:
-        # recover from error (repeat last command, check data
-        # consistency or just continue − depends on the task)
-        print('Error: {}'.format(e))
-        print('Last value: {}'.format(test_value))
-        print('Reconnecting')
+        my_cache = client.get_or_create_cache({
+            PROP_NAME: 'my_cache',
+            PROP_CACHE_MODE: CacheMode.PARTITIONED,
+            PROP_BACKUPS_NUMBER: 2,
+        })
+        my_cache.put('test_key', 0)
+        test_value = 0
+
+        # abstract main loop
+        while True:
+            try:
+                # do the work
+                test_value = my_cache.get('test_key') or 0
+                my_cache.put('test_key', test_value + 1)
+            except (OSError, SocketError) as e:
+                # recover from error (repeat last command, check data
+                # consistency or just continue − depends on the task)
+                print(f'Error: {e}')
+                print(f'Last value: {test_value}')
+                print('Reconnecting')
 
 # Connected
 # Error: Connection broken.
diff --git a/examples/get_and_put.py b/examples/get_and_put.py
index 49c5108..053e4b7 100644
--- a/examples/get_and_put.py
+++ b/examples/get_and_put.py
@@ -16,26 +16,24 @@
 from pyignite import Client
 
 client = Client()
-client.connect('127.0.0.1', 10800)
+with client.connect('127.0.0.1', 10800):
+    my_cache = client.create_cache('my cache')
 
-my_cache = client.create_cache('my cache')
+    my_cache.put('my key', 42)
 
-my_cache.put('my key', 42)
+    result = my_cache.get('my key')
+    print(result)  # 42
 
-result = my_cache.get('my key')
-print(result)  # 42
+    result = my_cache.get('non-existent key')
+    print(result)  # None
 
-result = my_cache.get('non-existent key')
-print(result)  # None
+    result = my_cache.get_all([
+        'my key',
+        'non-existent key',
+        'other-key',
+    ])
+    print(result)  # {'my key': 42}
 
-result = my_cache.get_all([
-    'my key',
-    'non-existent key',
-    'other-key',
-])
-print(result)  # {'my key': 42}
+    my_cache.clear_key('my key')
 
-my_cache.clear_key('my key')
-
-my_cache.destroy()
-client.close()
+    my_cache.destroy()
diff --git a/examples/get_and_put_complex.py b/examples/get_and_put_complex.py
index 2444612..cff0c2f 100644
--- a/examples/get_and_put_complex.py
+++ b/examples/get_and_put_complex.py
@@ -16,53 +16,52 @@
 from collections import OrderedDict
 
 from pyignite import Client
-from pyignite.datatypes import (
-    CollectionObject, MapObject, ObjectArrayObject,
-)
+from pyignite.datatypes import CollectionObject, MapObject, ObjectArrayObject
 
 
 client = Client()
-client.connect('127.0.0.1', 10800)
+with client.connect('127.0.0.1', 10800):
+    my_cache = client.get_or_create_cache('my cache')
 
-my_cache = client.get_or_create_cache('my cache')
+    value = OrderedDict([(1, 'test'), ('key', 2.0)])
 
-value = OrderedDict([(1, 'test'), ('key', 2.0)])
+    # saving ordered dictionary
+    type_id = MapObject.LINKED_HASH_MAP
+    my_cache.put('my dict', (type_id, value))
+    result = my_cache.get('my dict')
+    print(result)  # (2, OrderedDict([(1, 'test'), ('key', 2.0)]))
 
-# saving ordered dictionary
-type_id = MapObject.LINKED_HASH_MAP
-my_cache.put('my dict', (type_id, value))
-result = my_cache.get('my dict')
-print(result)  # (2, OrderedDict([(1, 'test'), ('key', 2.0)]))
+    # saving unordered dictionary
+    type_id = MapObject.HASH_MAP
+    my_cache.put('my dict', (type_id, value))
+    result = my_cache.get('my dict')
+    print(result)  # (1, {'key': 2.0, 1: 'test'})
 
-# saving unordered dictionary
-type_id = MapObject.HASH_MAP
-my_cache.put('my dict', (type_id, value))
-result = my_cache.get('my dict')
-print(result)  # (1, {'key': 2.0, 1: 'test'})
+    type_id = CollectionObject.LINKED_LIST
+    value = [1, '2', 3.0]
 
-type_id = CollectionObject.LINKED_LIST
-value = [1, '2', 3.0]
+    my_cache.put('my list', (type_id, value))
 
-my_cache.put('my list', (type_id, value))
+    result = my_cache.get('my list')
+    print(result)  # (2, [1, '2', 3.0])
 
-result = my_cache.get('my list')
-print(result)  # (2, [1, '2', 3.0])
+    type_id = CollectionObject.HASH_SET
+    value = [4, 4, 'test', 5.6]
 
-type_id = CollectionObject.HASH_SET
-value = [4, 4, 'test', 5.6]
+    my_cache.put('my set', (type_id, value))
 
-my_cache.put('my set', (type_id, value))
+    result = my_cache.get('my set')
+    print(result)  # (3, [5.6, 4, 'test'])
 
-result = my_cache.get('my set')
-print(result)  # (3, [5.6, 4, 'test'])
+    type_id = ObjectArrayObject.OBJECT
+    value = [7, '8', 9.0]
 
-type_id = ObjectArrayObject.OBJECT
-value = [7, '8', 9.0]
+    my_cache.put(
+        'my array of objects',
+        (type_id, value),
+        value_hint=ObjectArrayObject  # this hint is mandatory!
+    )
+    result = my_cache.get('my array of objects')
+    print(result)  # (-1, [7, '8', 9.0])
 
-my_cache.put(
-    'my array of objects',
-    (type_id, value),
-    value_hint=ObjectArrayObject  # this hint is mandatory!
-)
-result = my_cache.get('my array of objects')
-print(result)  # (-1, [7, '8', 9.0])
+    my_cache.destroy()
diff --git a/examples/migrate_binary.py b/examples/migrate_binary.py
index f0b0f74..c22fa4f 100644
--- a/examples/migrate_binary.py
+++ b/examples/migrate_binary.py
@@ -106,18 +106,18 @@
 
 
 client = Client()
-client.connect('127.0.0.1', 10800)
 
-accounting = client.get_or_create_cache('accounting')
+with client.connect('127.0.0.1', 10800):
+    accounting = client.get_or_create_cache('accounting')
 
-for key, value in old_data:
-    accounting.put(key, ExpenseVoucher(**value))
+    for key, value in old_data:
+        accounting.put(key, ExpenseVoucher(**value))
 
-data_classes = client.query_binary_type('ExpenseVoucher')
-print(data_classes)
-# {
-#     -231598180: <class '__main__.ExpenseVoucher'>
-# }
+    data_classes = client.query_binary_type('ExpenseVoucher')
+    print(data_classes)
+    # {
+    #     -231598180: <class '__main__.ExpenseVoucher'>
+    # }
 
 s_id, data_class = data_classes.popitem()
 schema = data_class.schema
@@ -182,9 +182,11 @@
 
 
 # migrate data
-result = accounting.scan()
-migrate(accounting, result, ExpenseVoucherV2)
+with client.connect('127.0.0.1', 10800):
+    accounting = client.get_or_create_cache('accounting')
 
-# cleanup
-accounting.destroy()
-client.close()
+    with accounting.scan() as cursor:
+        migrate(accounting, cursor, ExpenseVoucherV2)
+
+    # cleanup
+    accounting.destroy()
diff --git a/examples/read_binary.py b/examples/read_binary.py
index 3a8e9e2..fe642d8 100644
--- a/examples/read_binary.py
+++ b/examples/read_binary.py
@@ -16,7 +16,7 @@
 from decimal import Decimal
 
 from pyignite import Client
-from pyignite.datatypes.prop_codes import *
+from pyignite.datatypes.prop_codes import PROP_NAME, PROP_QUERY_ENTITIES
 
 
 COUNTRY_TABLE_NAME = 'Country'
@@ -194,82 +194,98 @@
 
 # establish connection
 client = Client()
-client.connect('127.0.0.1', 10800)
+with client.connect('127.0.0.1', 10800):
 
-# create tables
-for query in [
-    COUNTRY_CREATE_TABLE_QUERY,
-    CITY_CREATE_TABLE_QUERY,
-    LANGUAGE_CREATE_TABLE_QUERY,
-]:
-    client.sql(query)
+    # create tables
+    for query in [
+        COUNTRY_CREATE_TABLE_QUERY,
+        CITY_CREATE_TABLE_QUERY,
+        LANGUAGE_CREATE_TABLE_QUERY,
+    ]:
+        client.sql(query)
 
-# create indices
-for query in [CITY_CREATE_INDEX, LANGUAGE_CREATE_INDEX]:
-    client.sql(query)
+    # create indices
+    for query in [CITY_CREATE_INDEX, LANGUAGE_CREATE_INDEX]:
+        client.sql(query)
 
-# load data
-for row in COUNTRY_DATA:
-    client.sql(COUNTRY_INSERT_QUERY, query_args=row)
+    # load data
+    for row in COUNTRY_DATA:
+        client.sql(COUNTRY_INSERT_QUERY, query_args=row)
 
-for row in CITY_DATA:
-    client.sql(CITY_INSERT_QUERY, query_args=row)
+    for row in CITY_DATA:
+        client.sql(CITY_INSERT_QUERY, query_args=row)
 
-for row in LANGUAGE_DATA:
-    client.sql(LANGUAGE_INSERT_QUERY, query_args=row)
+    for row in LANGUAGE_DATA:
+        client.sql(LANGUAGE_INSERT_QUERY, query_args=row)
 
-# examine the storage
-result = client.get_cache_names()
-print(result)
-# [
-#     'SQL_PUBLIC_CITY',
-#     'SQL_PUBLIC_COUNTRY',
-#     'PUBLIC',
-#     'SQL_PUBLIC_COUNTRYLANGUAGE'
-# ]
+    # examine the storage
+    result = client.get_cache_names()
+    print(result)
+    # [
+    #     'SQL_PUBLIC_CITY',
+    #     'SQL_PUBLIC_COUNTRY',
+    #     'PUBLIC',
+    #     'SQL_PUBLIC_COUNTRYLANGUAGE'
+    # ]
 
-city_cache = client.get_or_create_cache('SQL_PUBLIC_CITY')
-print(city_cache.settings[PROP_NAME])
-# 'SQL_PUBLIC_CITY'
+    city_cache = client.get_or_create_cache('SQL_PUBLIC_CITY')
+    print(city_cache.settings[PROP_NAME])
+    # 'SQL_PUBLIC_CITY'
 
-print(city_cache.settings[PROP_QUERY_ENTITIES])
-# {
-#     'key_type_name': (
-#         'SQL_PUBLIC_CITY_9ac8e17a_2f99_45b7_958e_06da32882e9d_KEY'
-#     ),
-#     'value_type_name': (
-#         'SQL_PUBLIC_CITY_9ac8e17a_2f99_45b7_958e_06da32882e9d'
-#     ),
-#     'table_name': 'CITY',
-#     'query_fields': [
-#         ...
-#     ],
-#     'field_name_aliases': [
-#         ...
-#     ],
-#     'query_indexes': []
-# }
+    print(city_cache.settings[PROP_QUERY_ENTITIES])
+    # {
+    #     'key_type_name': (
+    #         'SQL_PUBLIC_CITY_9ac8e17a_2f99_45b7_958e_06da32882e9d_KEY'
+    #     ),
+    #     'value_type_name': (
+    #         'SQL_PUBLIC_CITY_9ac8e17a_2f99_45b7_958e_06da32882e9d'
+    #     ),
+    #     'table_name': 'CITY',
+    #     'query_fields': [
+    #         ...
+    #     ],
+    #     'field_name_aliases': [
+    #         ...
+    #     ],
+    #     'query_indexes': []
+    # }
 
-result = city_cache.scan()
-print(next(result))
-# (
-#     SQL_PUBLIC_CITY_6fe650e1_700f_4e74_867d_58f52f433c43_KEY(
-#         ID=1890,
-#         COUNTRYCODE='CHN',
-#         version=1
-#     ),
-#     SQL_PUBLIC_CITY_6fe650e1_700f_4e74_867d_58f52f433c43(
-#         NAME='Shanghai',
-#         DISTRICT='Shanghai',
-#         POPULATION=9696300,
-#         version=1
-#     )
-# )
+    with city_cache.scan() as cursor:
+        print(next(cursor))
+    # (
+    #     SQL_PUBLIC_CITY_6fe650e1_700f_4e74_867d_58f52f433c43_KEY(
+    #         ID=1890,
+    #         COUNTRYCODE='CHN',
+    #         version=1
+    #     ),
+    #     SQL_PUBLIC_CITY_6fe650e1_700f_4e74_867d_58f52f433c43(
+    #         NAME='Shanghai',
+    #         DISTRICT='Shanghai',
+    #         POPULATION=9696300,
+    #         version=1
+    #     )
+    # )
 
-# clean up
-for table_name in [
-    CITY_TABLE_NAME,
-    LANGUAGE_TABLE_NAME,
-    COUNTRY_TABLE_NAME,
-]:
-    result = client.sql(DROP_TABLE_QUERY.format(table_name))
+    with client.sql('SELECT _KEY, _VAL FROM CITY WHERE ID = ?', query_args=[1890]) as cursor:
+        print(next(cursor))
+    # (
+    #     SQL_PUBLIC_CITY_6fe650e1_700f_4e74_867d_58f52f433c43_KEY(
+    #         ID=1890,
+    #         COUNTRYCODE='CHN',
+    #         version=1
+    #     ),
+    #     SQL_PUBLIC_CITY_6fe650e1_700f_4e74_867d_58f52f433c43(
+    #         NAME='Shanghai',
+    #         DISTRICT='Shanghai',
+    #         POPULATION=9696300,
+    #         version=1
+    #     )
+    # )
+
+    # clean up
+    for table_name in [
+        CITY_TABLE_NAME,
+        LANGUAGE_TABLE_NAME,
+        COUNTRY_TABLE_NAME,
+    ]:
+        result = client.sql(DROP_TABLE_QUERY.format(table_name))
diff --git a/examples/readme.md b/examples/readme.md
index 3628c82..3caf6c1 100644
--- a/examples/readme.md
+++ b/examples/readme.md
@@ -4,11 +4,11 @@
 
 - `binary_basics.py` − basic operations with Complex objects,
 - `binary_types.py` - read SQL table as a key-value cache,
-- `create_binary.py` − create SQL row with key-value operation,
+- `create_binary.py` − create SQL row with key-value operation,
 - `failover.py` − fail-over connection to Ignite cluster,
-- `get_and_put.py` − basic key-value operations,
+- `get_and_put.py` − basic key-value operations,
 - `migrate_binary.py` − work with Complex object schemas,
-- `scans.py` − cache scan operation,
+- `scans.py` − cache scan operation,
 - `sql.py` − use Ignite SQL,
 - `type_hints.py` − type hints.
 
diff --git a/examples/scans.py b/examples/scans.py
index d5f2b48..eaafa6e 100644
--- a/examples/scans.py
+++ b/examples/scans.py
@@ -16,40 +16,37 @@
 from pyignite import Client
 
 client = Client()
-client.connect('127.0.0.1', 10800)
+with client.connect('127.0.0.1', 10800):
+    my_cache = client.create_cache('my cache')
+    my_cache.put_all({'key_{}'.format(v): v for v in range(20)})
+    # {
+    #     'key_0': 0,
+    #     'key_1': 1,
+    #     'key_2': 2,
+    #     ... 20 elements in total...
+    #     'key_18': 18,
+    #     'key_19': 19
+    # }
 
-my_cache = client.create_cache('my cache')
+    with my_cache.scan() as cursor:
+        for k, v in cursor:
+            print(k, v)
+    # 'key_17' 17
+    # 'key_10' 10
+    # 'key_6' 6,
+    # ... 20 elements in total...
+    # 'key_16' 16
+    # 'key_12' 12
 
-my_cache.put_all({'key_{}'.format(v): v for v in range(20)})
-# {
-#     'key_0': 0,
-#     'key_1': 1,
-#     'key_2': 2,
-#     ... 20 elements in total...
-#     'key_18': 18,
-#     'key_19': 19
-# }
+    with my_cache.scan() as cursor:
+        print(dict(cursor))
+    # {
+    #     'key_17': 17,
+    #     'key_10': 10,
+    #     'key_6': 6,
+    #     ... 20 elements in total...
+    #     'key_16': 16,
+    #     'key_12': 12
+    # }
 
-result = my_cache.scan()
-for k, v in result:
-    print(k, v)
-# 'key_17' 17
-# 'key_10' 10
-# 'key_6' 6,
-# ... 20 elements in total...
-# 'key_16' 16
-# 'key_12' 12
-
-result = my_cache.scan()
-print(dict(result))
-# {
-#     'key_17': 17,
-#     'key_10': 10,
-#     'key_6': 6,
-#     ... 20 elements in total...
-#     'key_16': 16,
-#     'key_12': 12
-# }
-
-my_cache.destroy()
-client.close()
+    my_cache.destroy()
diff --git a/examples/sql.py b/examples/sql.py
index 0e8c729..d81ff26 100644
--- a/examples/sql.py
+++ b/examples/sql.py
@@ -193,106 +193,99 @@
 
 # establish connection
 client = Client()
-client.connect('127.0.0.1', 10800)
+with client.connect('127.0.0.1', 10800):
 
-# create tables
-for query in [
-    COUNTRY_CREATE_TABLE_QUERY,
-    CITY_CREATE_TABLE_QUERY,
-    LANGUAGE_CREATE_TABLE_QUERY,
-]:
-    client.sql(query)
+    # create tables
+    for query in [
+        COUNTRY_CREATE_TABLE_QUERY,
+        CITY_CREATE_TABLE_QUERY,
+        LANGUAGE_CREATE_TABLE_QUERY,
+    ]:
+        client.sql(query)
 
-# create indices
-for query in [CITY_CREATE_INDEX, LANGUAGE_CREATE_INDEX]:
-    client.sql(query)
+    # create indices
+    for query in [CITY_CREATE_INDEX, LANGUAGE_CREATE_INDEX]:
+        client.sql(query)
 
-# load data
-for row in COUNTRY_DATA:
-    client.sql(COUNTRY_INSERT_QUERY, query_args=row)
+    # load data
+    for row in COUNTRY_DATA:
+        client.sql(COUNTRY_INSERT_QUERY, query_args=row)
 
-for row in CITY_DATA:
-    client.sql(CITY_INSERT_QUERY, query_args=row)
+    for row in CITY_DATA:
+        client.sql(CITY_INSERT_QUERY, query_args=row)
 
-for row in LANGUAGE_DATA:
-    client.sql(LANGUAGE_INSERT_QUERY, query_args=row)
+    for row in LANGUAGE_DATA:
+        client.sql(LANGUAGE_INSERT_QUERY, query_args=row)
 
-# 10 most populated cities (with pagination)
-MOST_POPULATED_QUERY = '''
-SELECT name, population FROM City ORDER BY population DESC LIMIT 10'''
+    # 10 most populated cities (with pagination)
+    MOST_POPULATED_QUERY = '''
+    SELECT name, population FROM City ORDER BY population DESC LIMIT 10'''
 
-result = client.sql(MOST_POPULATED_QUERY)
-print('Most 10 populated cities:')
-for row in result:
-    print(row)
-# Most 10 populated cities:
-# ['Mumbai (Bombay)', 10500000]
-# ['Shanghai', 9696300]
-# ['New York', 8008278]
-# ['Peking', 7472000]
-# ['Delhi', 7206704]
-# ['Chongqing', 6351600]
-# ['Tianjin', 5286800]
-# ['Calcutta [Kolkata]', 4399819]
-# ['Wuhan', 4344600]
-# ['Harbin', 4289800]
+    with client.sql(MOST_POPULATED_QUERY) as cursor:
+        print('Most 10 populated cities:')
+        for row in cursor:
+            print(row)
+    # Most 10 populated cities:
+    # ['Mumbai (Bombay)', 10500000]
+    # ['Shanghai', 9696300]
+    # ['New York', 8008278]
+    # ['Peking', 7472000]
+    # ['Delhi', 7206704]
+    # ['Chongqing', 6351600]
+    # ['Tianjin', 5286800]
+    # ['Calcutta [Kolkata]', 4399819]
+    # ['Wuhan', 4344600]
+    # ['Harbin', 4289800]
 
-# 10 most populated cities in 3 countries (with pagination and header row)
-MOST_POPULATED_IN_3_COUNTRIES_QUERY = '''
-SELECT country.name as country_name, city.name as city_name, MAX(city.population) AS max_pop FROM country
-    JOIN city ON city.countrycode = country.code
-    WHERE country.code IN ('USA','IND','CHN')
-    GROUP BY country.name, city.name ORDER BY max_pop DESC LIMIT 10
-'''
+    # 10 most populated cities in 3 countries (with pagination and header row)
+    MOST_POPULATED_IN_3_COUNTRIES_QUERY = '''
+    SELECT country.name as country_name, city.name as city_name, MAX(city.population) AS max_pop FROM country
+        JOIN city ON city.countrycode = country.code
+        WHERE country.code IN ('USA','IND','CHN')
+        GROUP BY country.name, city.name ORDER BY max_pop DESC LIMIT 10
+    '''
 
-result = client.sql(
-    MOST_POPULATED_IN_3_COUNTRIES_QUERY,
-    include_field_names=True,
-)
-print('Most 10 populated cities in USA, India and China:')
-print(next(result))
-print('----------------------------------------')
-for row in result:
-    print(row)
-# Most 10 populated cities in USA, India and China:
-# ['COUNTRY_NAME', 'CITY_NAME', 'MAX_POP']
-# ----------------------------------------
-# ['India', 'Mumbai (Bombay)', 10500000]
-# ['China', 'Shanghai', 9696300]
-# ['United States', 'New York', 8008278]
-# ['China', 'Peking', 7472000]
-# ['India', 'Delhi', 7206704]
-# ['China', 'Chongqing', 6351600]
-# ['China', 'Tianjin', 5286800]
-# ['India', 'Calcutta [Kolkata]', 4399819]
-# ['China', 'Wuhan', 4344600]
-# ['China', 'Harbin', 4289800]
+    with client.sql(MOST_POPULATED_IN_3_COUNTRIES_QUERY, include_field_names=True) as cursor:
+        print('Most 10 populated cities in USA, India and China:')
+        print(next(cursor))
+        print('----------------------------------------')
+        for row in cursor:
+            print(row)
+    # Most 10 populated cities in USA, India and China:
+    # ['COUNTRY_NAME', 'CITY_NAME', 'MAX_POP']
+    # ----------------------------------------
+    # ['India', 'Mumbai (Bombay)', 10500000]
+    # ['China', 'Shanghai', 9696300]
+    # ['United States', 'New York', 8008278]
+    # ['China', 'Peking', 7472000]
+    # ['India', 'Delhi', 7206704]
+    # ['China', 'Chongqing', 6351600]
+    # ['China', 'Tianjin', 5286800]
+    # ['India', 'Calcutta [Kolkata]', 4399819]
+    # ['China', 'Wuhan', 4344600]
+    # ['China', 'Harbin', 4289800]
 
-# show city info
-CITY_INFO_QUERY = '''SELECT * FROM City WHERE id = ?'''
+    # show city info
+    CITY_INFO_QUERY = '''SELECT * FROM City WHERE id = ?'''
 
-result = client.sql(
-    CITY_INFO_QUERY,
-    query_args=[3802],
-    include_field_names=True,
-)
-field_names = next(result)
-field_data = list(*result)
+    with client.sql(CITY_INFO_QUERY, query_args=[3802], include_field_names=True) as cursor:
+        field_names = next(cursor)
+        field_data = list(*cursor)
 
-print('City info:')
-for field_name, field_value in zip(field_names * len(field_data), field_data):
-    print('{}: {}'.format(field_name, field_value))
-# City info:
-# ID: 3802
-# NAME: Detroit
-# COUNTRYCODE: USA
-# DISTRICT: Michigan
-# POPULATION: 951270
+        print('City info:')
+        for field_name, field_value in zip(field_names * len(field_data), field_data):
+            print('{}: {}'.format(field_name, field_value))
+    # City info:
+    # ID: 3802
+    # NAME: Detroit
+    # COUNTRYCODE: USA
+    # DISTRICT: Michigan
+    # POPULATION: 951270
 
-# clean up
-for table_name in [
-    CITY_TABLE_NAME,
-    LANGUAGE_TABLE_NAME,
-    COUNTRY_TABLE_NAME,
-]:
-    result = client.sql(DROP_TABLE_QUERY.format(table_name))
+    # clean up
+    for table_name in [
+        CITY_TABLE_NAME,
+        LANGUAGE_TABLE_NAME,
+        COUNTRY_TABLE_NAME,
+    ]:
+        result = client.sql(DROP_TABLE_QUERY.format(table_name))
diff --git a/examples/type_hints.py b/examples/type_hints.py
index 4cc44c0..8d53bf9 100644
--- a/examples/type_hints.py
+++ b/examples/type_hints.py
@@ -17,35 +17,34 @@
 from pyignite.datatypes import CharObject, ShortObject
 
 client = Client()
-client.connect('127.0.0.1', 10800)
+with client.connect('127.0.0.1', 10800):
 
-my_cache = client.get_or_create_cache('my cache')
+    my_cache = client.get_or_create_cache('my cache')
 
-my_cache.put('my key', 42)
-# value ‘42’ takes 9 bytes of memory as a LongObject
+    my_cache.put('my key', 42)
+    # value ‘42’ takes 9 bytes of memory as a LongObject
 
-my_cache.put('my key', 42, value_hint=ShortObject)
-# value ‘42’ takes only 3 bytes as a ShortObject
+    my_cache.put('my key', 42, value_hint=ShortObject)
+    # value ‘42’ takes only 3 bytes as a ShortObject
 
-my_cache.put('a', 1)
-# ‘a’ is a key of type String
+    my_cache.put('a', 1)
+    # ‘a’ is a key of type String
 
-my_cache.put('a', 2, key_hint=CharObject)
-# another key ‘a’ of type CharObject was created
+    my_cache.put('a', 2, key_hint=CharObject)
+    # another key ‘a’ of type CharObject was created
 
-value = my_cache.get('a')
-print(value)
-# 1
+    value = my_cache.get('a')
+    print(value)
+    # 1
 
-value = my_cache.get('a', key_hint=CharObject)
-print(value)
-# 2
+    value = my_cache.get('a', key_hint=CharObject)
+    print(value)
+    # 2
 
-# now let us delete both keys at once
-my_cache.remove_keys([
-    'a',                # a default type key
-    ('a', CharObject),  # a key of type CharObject
-])
+    # now let us delete both keys at once
+    my_cache.remove_keys([
+        'a',                # a default type key
+        ('a', CharObject),  # a key of type CharObject
+    ])
 
-my_cache.destroy()
-client.close()
+    my_cache.destroy()
diff --git a/pyignite/aio_cache.py b/pyignite/aio_cache.py
index 24d4bce..32f2cb2 100644
--- a/pyignite/aio_cache.py
+++ b/pyignite/aio_cache.py
@@ -471,7 +471,7 @@
         conn = await self._get_best_node()
         return await cache_get_size_async(conn, self._cache_id, peek_modes)
 
-    def scan(self, page_size: int = 1, partitions: int = -1, local: bool = False):
+    def scan(self, page_size: int = 1, partitions: int = -1, local: bool = False) -> AioScanCursor:
         """
         Returns all key-value pairs from the cache, similar to `get_all`, but
         with internal pagination, which is slower, but safer.
diff --git a/pyignite/aio_client.py b/pyignite/aio_client.py
index 1870878..7a5959d 100644
--- a/pyignite/aio_client.py
+++ b/pyignite/aio_client.py
@@ -282,7 +282,7 @@
         of an intermittent error (most probably “Getting affinity for topology
         version earlier than affinity is calculated”).
 
-        :param conn: connection to Igneite server,
+        :param conn: connection to Ignite server,
         :param caches: Ids of caches,
         :return: OP_CACHE_PARTITIONS operation result value.
         """
@@ -414,7 +414,7 @@
         lazy: bool = False, include_field_names: bool = False,
         max_rows: int = -1, timeout: int = 0,
         cache: Union[int, str, 'AioCache'] = None
-    ):
+    ) -> AioSqlFieldsCursor:
         """
         Runs an SQL query and returns its result.
 
@@ -447,9 +447,9 @@
          (all rows),
         :param timeout: (optional) non-negative timeout value in ms.
          Zero disables timeout (default),
-        :param cache (optional) Name or ID of the cache to use to infer schema.
+        :param cache: (optional) Name or ID of the cache to use to infer schema.
          If set, 'schema' argument is ignored,
-        :return: generator with result rows as a lists. If
+        :return: async sql fields cursor with result rows as a lists. If
          `include_field_names` was set, the first row will hold field names.
         """
 
diff --git a/pyignite/cache.py b/pyignite/cache.py
index f00f000..3c93637 100644
--- a/pyignite/cache.py
+++ b/pyignite/cache.py
@@ -577,7 +577,7 @@
             self._get_best_node(), self._cache_id, peek_modes
         )
 
-    def scan(self, page_size: int = 1, partitions: int = -1, local: bool = False):
+    def scan(self, page_size: int = 1, partitions: int = -1, local: bool = False) -> ScanCursor:
         """
         Returns all key-value pairs from the cache, similar to `get_all`, but
         with internal pagination, which is slower, but safer.
@@ -596,7 +596,7 @@
             self, query_str: str, page_size: int = 1,
             query_args: Optional[list] = None, distributed_joins: bool = False,
             replicated_only: bool = False, local: bool = False, timeout: int = 0
-    ):
+    ) -> SqlCursor:
         """
         Executes a simplified SQL SELECT query over data stored in the cache.
         The query returns the whole record (key and value).
diff --git a/pyignite/client.py b/pyignite/client.py
index b7c4046..17e9d80 100644
--- a/pyignite/client.py
+++ b/pyignite/client.py
@@ -681,7 +681,7 @@
         lazy: bool = False, include_field_names: bool = False,
         max_rows: int = -1, timeout: int = 0,
         cache: Union[int, str, Cache] = None
-    ):
+    ) -> SqlFieldsCursor:
         """
         Runs an SQL query and returns its result.
 
@@ -693,9 +693,9 @@
         :param schema: (optional) schema for the query. Defaults to `PUBLIC`,
         :param statement_type: (optional) statement type. Can be:
 
-         * StatementType.ALL − any type (default),
+         * StatementType.ALL − any type (default),
          * StatementType.SELECT − select,
-         * StatementType.UPDATE − update.
+         * StatementType.UPDATE − update.
 
         :param distributed_joins: (optional) distributed joins. Defaults
          to False,
@@ -714,9 +714,9 @@
          (all rows),
         :param timeout: (optional) non-negative timeout value in ms.
          Zero disables timeout (default),
-        :param cache (optional) Name or ID of the cache to use to infer schema.
+        :param cache: (optional) Name or ID of the cache to use to infer schema.
          If set, 'schema' argument is ignored,
-        :return: generator with result rows as a lists. If
+        :return: sql fields cursor with result rows as a lists. If
          `include_field_names` was set, the first row will hold field names.
         """
 
diff --git a/pyignite/cursors.py b/pyignite/cursors.py
index c699556..0a8f0b0 100644
--- a/pyignite/cursors.py
+++ b/pyignite/cursors.py
@@ -32,6 +32,9 @@
 class BaseCursorMixin:
     @property
     def connection(self):
+        """
+        Ignite cluster connection.
+        """
         return getattr(self, '_conn', None)
 
     @connection.setter
@@ -40,6 +43,9 @@
 
     @property
     def cursor_id(self):
+        """
+        Cursor id.
+        """
         return getattr(self, '_cursor_id', None)
 
     @cursor_id.setter
@@ -48,6 +54,9 @@
 
     @property
     def more(self):
+        """
+        Whether cursor has more values.
+        """
         return getattr(self, '_more', None)
 
     @more.setter
@@ -56,6 +65,9 @@
 
     @property
     def cache_id(self):
+        """
+        Cache id.
+        """
         return getattr(self, '_cache_id', None)
 
     @cache_id.setter
@@ -64,6 +76,9 @@
 
     @property
     def client(self):
+        """
+        Apache Ignite client.
+        """
         return getattr(self, '_client', None)
 
     @client.setter
@@ -72,6 +87,9 @@
 
     @property
     def data(self):
+        """
+        Current fetched data.
+        """
         return getattr(self, '_data', None)
 
     @data.setter
@@ -90,6 +108,9 @@
         self.close()
 
     def close(self):
+        """
+        Close cursor.
+        """
         if self.connection and self.cursor_id and self.more:
             resource_close(self.connection, self.cursor_id)
 
@@ -105,6 +126,9 @@
         await self.close()
 
     async def close(self):
+        """
+        Close cursor.
+        """
         if self.connection and self.cursor_id and self.more:
             await resource_close_async(self.connection, self.cursor_id)
 
@@ -132,7 +156,17 @@
 
 
 class ScanCursor(AbstractScanCursor, CursorMixin):
+    """
+    Synchronous scan cursor.
+    """
     def __init__(self, client, cache_id, page_size, partitions, local):
+        """
+        :param client: Synchronous Apache Ignite client.
+        :param cache_id: Cache id.
+        :param page_size: page size.
+        :param partitions: number of partitions to query (negative to query entire cache).
+        :param local: pass True if this query should be executed on local node only.
+        """
         super().__init__(client, cache_id, page_size, partitions, local)
 
         self.connection = self.client.random_node
@@ -156,7 +190,17 @@
 
 
 class AioScanCursor(AbstractScanCursor, AioCursorMixin):
+    """
+    Asynchronous scan query cursor.
+    """
     def __init__(self, client, cache_id, page_size, partitions, local):
+        """
+        :param client: Asynchronous Apache Ignite client.
+        :param cache_id: Cache id.
+        :param page_size: page size.
+        :param partitions: number of partitions to query (negative to query entire cache).
+        :param local: pass True if this query should be executed on local node only.
+        """
         super().__init__(client, cache_id, page_size, partitions, local)
 
     async def __aenter__(self):
@@ -191,7 +235,14 @@
 
 
 class SqlCursor(CursorMixin):
+    """
+    Synchronous SQL query cursor.
+    """
     def __init__(self, client, cache_id, *args, **kwargs):
+        """
+        :param client: Synchronous Apache Ignite client.
+        :param cache_id: Cache id.
+        """
         self.client = client
         self.cache_id = cache_id
         self.connection = self.client.random_node
@@ -241,7 +292,14 @@
 
 
 class SqlFieldsCursor(AbstractSqlFieldsCursor, CursorMixin):
+    """
+    Synchronous SQL fields query cursor.
+    """
     def __init__(self, client, cache_id, *args, **kwargs):
+        """
+        :param client: Synchronous Apache Ignite client.
+        :param cache_id: Cache id.
+        """
         super().__init__(client, cache_id)
         self.connection = self.client.random_node
         self._finalize_init(sql_fields(self.connection, self.cache_id, *args, **kwargs))
@@ -273,7 +331,14 @@
 
 
 class AioSqlFieldsCursor(AbstractSqlFieldsCursor, AioCursorMixin):
+    """
+    Asynchronous SQL fields query cursor.
+    """
     def __init__(self, client, cache_id, *args, **kwargs):
+        """
+        :param client: Synchronous Apache Ignite client.
+        :param cache_id: Cache id.
+        """
         super().__init__(client, cache_id)
         self._params = (args, kwargs)