5. Extending kamaki.clients

By default, kamaki clients implement REST APIs, therefore they manage HTTP requests and responses to communicate with services.

5.1. How to build a client

All service clients consist of a subclass of the Client class and implement separate client functionalities as member methods. There is also an error class to raise exceptions that can be handled by kamaki interfaces.

#  ${KAMAKI_PATH}/kamaki/clients/mynewclient.py

from kamaki.clients import Client, ClientError


class MyNewClient(Client):
    """MyNewClient Description Here"""

    def my_first_method(self, **args):
        """Method description"""
        try:
            ...
            method code
            ...
        except SomeKnownException as e1:
            raise ClientError('MyError: %s' % e1)
        except SomeOtherException as e2:
            raise ClientError('MyError: %s' % e2)

    def my_second_method(self, **params):
        """Method description"""
        ...

Custom clients can use a set of convenience methods for easy HTTP requests

def get(self, path, **kwargs)
def head(self, path, **kwargs)
def post(self, path, **kwargs)
def put(self, path, **kwargs)
def delete(self, path, **kwargs)
def copy(self, path, **kwargs)
def move(self, path, **kwargs)

5.2. How to use your client

External applications must instantiate a MyNewClient object.

from kamaki.clients import ClientError
from kamaki.clients.mynewclient import MyNewClient

...
try:
    cl = MyNewClient(args)
    cl.my_first_method(other_args)
except ClientError as cle:
    print('Client Error: %s' % cle)
...

5.3. Concurrency control

Kamaki clients may handle multiple requests at once, using threads. In that case, users might implement their own thread handling mechanism, use an external solution or take advantage of the mechanism featured in kamaki.clients

from threading import enumerate
from kamaki.clients import SilentEvent
...

class MyNewClient(Client):
    ...

    def _single_threaded_method(self, **args):
        ...
        request code
        ...

    def multithread_method(self):
        thread_list = []
        self._init_thread_limit()
        while some_condition or thread_list:
            ...
            event = SilentEvent(self._single_threaded_method, **args)
            event.start()
            thread_list.append(event)
            thread_list = self._watch_thread_limit(thread_list)

5.4. Going agile

The kamaki.clients package contains a set of fine-grained unit-tests for all APIs.

Note

unit tests require the optional python-mock package, version 1.X or better

5.4.1. Using the tests

To run the tests, the kamaki source code has to be downloaded.

$ git clone https://code.grnet.gr/git/kamaki
$ cd kamaki/kamaki/clients

In each package under kamaki.clients, there is a test module (test.py) where the tests are implemented. To run all tests, run the test.py file from kamaki.clients

$ python test.py

To test a specific class, add the class name as an argument. e.g., for the Client class:

$ python test.py Client

To test a specific method in a class, apply an extra argument, e.g., for the request method in the Client class:

$ python test.py Client request

Each package contains a test module (test.py) which is also runnable from the command line. e.g., in the pithos package there is a test module which contains, among others, the download sub-test:

$ cd pithos

# Run all kamaki.clients.pithos tests
$ python test.py

# Run all kamaki.clients.pithos.PithoClient tests
$ python test.py Pithos

# Test kamaki.clients.pithos.PithosClient.download
$ python test.py Pithos download

To fully test a specific package, run test.py from the package location. e.g., to test everything in kamaki.clients.pithos package:

$ cd pithos
$ python test.py

5.4.2. Mechanism

Each folder / package contains a test.py file, that represents the test module of this package. All test modules contain a set of classes that extent the TestCase class. They also contain a main method to run the tests.

By convention, testing classes have the same name as the class they test. Methods not grouped in classes are tested by classes named after their respective module.

For example, the kamaki.clients.pithos.PithosClient class is tested by the kamaki.clients.pithos.test.PithosClient class, while the methods in kamaki.clients.utils module are tested by kamaki.clients.utils.test.Utils.

5.4.3. Adding unit tests

After modifying or extending kamaki.clients method, classes, modules or packages, it is a good practice to also modify or extend the corresponding unit tests. What’s more, it is recommended to modify or implement the testing of new behavior before implementing the behavior itself. The goal is to preserve an 1 to 1 mapping between methods and their tests.

5.4.3.1. Modifying an existing method

In case of an existing method modification, the programmer has to modify the corresponding test as well. By convention, the test method is located in the test module under the same package, in a TestCase subclass that is named with a name similar to the package or class that contains the tested method.

Example: to modify kamaki.clients.pithos.PithosRestClient.object_get, the programmer has to also adjust the kamaki.clients.pithos.test.PithosRestClient.test.object_get method.

5.4.3.2. Adding a new method

To implement a new method in an existing class, developers are encouraged to implement the corresponding unit test first. In order to do that, they should find the testing class that is mapped to the class or module they working on.

Example: Adding list_special to kamaki.clients.astakos.AstakosClient, requires changes to kamaki.clients.astakos.test.AstakosClient too, as shown bellow:

# file: ${kamaki}/kamaki/clients/astakos/__init__.py

class AstakosClient(TestCase):
    ...
    def test_list_special(self):
        """Test the list_special method"""
        ...

5.4.3.3. Implementing a new class or module

Each class or module needs a seperate test sub-module. By convention, each class or module under kamaki.clients, should be located in a separate directory.

Example 1: To add a NewService class that implements kamaki.clients.Client:

  • create a new_service package and implement the unit tests in

    kamaki.clients.new_service.test:

$ mkdir new_service && touch new_service/test.py
  • create the file to code the package implementation:
$ touch new_service/__init__.py
  • Create the test class and methods in kamaki.clients.new_service.test
# file: ${kamaki}/kamaki/clients/new_service/test.py
from unittest import TestCase

class NewService(TestCase):

    def test_method1(self):
        ...
  • Create the NewService and its actual functionality in kamaki.clients.new_service
# file: ${kamaki}/kamaki/clients/new_service/__init__.py
from kamaki.clients import Client

class NewService(Client):

    def method1(self, ...):
        ...
  • Import the test class to kamaki.clients.test:
# file: ${kamaki}/kamaki/clients/test.py
from kamaki.clients.new_service.test import NewService

Note

If the new class or module is part of an existing sub-package, it is acceptable to append its testing class in the existing test.py file of the sub-package it belongs to. For example, the kamaki.clients.pithos.PithosClient and kamaki.clients.pithos.rest_api.PithosRestClient classes are tested by two different classes (PithosClient and PithosRestClient respectively) in the same module (kamaki.clients.pithos.test).