Rearange and complete developers guide
authorStavros Sachtouris <saxtouri@admin.grnet.gr>
Thu, 29 Nov 2012 14:51:36 +0000 (16:51 +0200)
committerStavros Sachtouris <saxtouri@admin.grnet.gr>
Thu, 29 Nov 2012 14:51:36 +0000 (16:51 +0200)
docs/developers.rst [deleted file]
docs/developers/adding-commands.rst [new file with mode: 0644]
docs/developers/clients-api.rst [new file with mode: 0644]
docs/developers/code.rst [moved from docs/cli.rst with 51% similarity]
docs/developers/connection.rst [moved from docs/connection.rst with 100% similarity]
docs/developers/extending-clients-api.rst [new file with mode: 0644]
docs/devguide.rst [new file with mode: 0644]
docs/index.rst
docs/installation.rst
kamaki/clients/pithos_rest_api.py

diff --git a/docs/developers.rst b/docs/developers.rst
deleted file mode 100644 (file)
index 1272ae3..0000000
+++ /dev/null
@@ -1,421 +0,0 @@
-For Developers
-==============
-
-Creating applications with kamaki API
--------------------------------------
-
-Kamaki features a clients API for building third-party client applications that communicate with OpenStack and / or Synnefo cloud services. The package is called kamaki.clients and contains a number of 
-
-A good example of an application build on kamaki.clients is kamaki.cli, the command line interface of kamaki. 
-
-Since synnefo services are build as OpenStack extensions, an inheritance approach has been chosen for implementing clients for both. In specific, the *compute*, *storage* and *image* modules are clients of the OS compute, OS storage and Glance APIs, respectively. On the contrary, all the other modules are Synnefo extensions (*cyclades* extents *compute*, *pithos* and *pithos_rest_api* extent *storage*) or novel synnefo services (e.g. *astakos*).
-
-Setup a client instance
-^^^^^^^^^^^^^^^^^^^^^^^
-
-External applications may instantiate one or more kamaki clients.
-
-.. code-block:: python
-    :emphasize-lines: 1
-
-    Example 1.1: Instantiate a Cyclades client
-
-
-    from kamaki.clients.cyclades import CycladesClient
-    from kamaki.clients.pithos import PithosClient
-
-    my_cyclades_client = CycladesClient(base_url, token)
-    my_pithos_client = PithosClient(base_url, token, account, container)
-
-.. note:: *cyclades* and *pithos* clients inherit all methods of *compute* and *storage* clients respectively. Separate compute or storage objects should be used only when implementing applications for strict OS Compute or OS Storage services.
-
-Use client methods
-^^^^^^^^^^^^^^^^^^
-
-Client methods can now be called. Developers are advised to consult :ref:`the-client-api-ref` for details on the available methods and how to use them.
-
-In the following example, the *cyclades* and *pithos* clients of example 1.1 are used to extract some information, that is then printed to the standard output.
-
-
-.. code-block:: python
-    :emphasize-lines: 1,2
-
-    Example 1.2: Print server name and OS for server with server_id
-                Print objects in container mycont
-
-
-    srv = my_cyclades_client.get_server_info(server_id)
-    print("Server Name: %s (with OS %s" % (srv['name'], srv['os']))
-
-    obj_list = my_pithos_client.list_objects(mycont)
-    for obj in obj_list:
-        print('  %s of %s bytes' % (obj['name'], obj['bytes']))
-
-.. code-block:: console
-    :emphasize-lines: 1
-
-    Run of examples 1.1 + 1.2
-
-
-    $ python test_script.py
-    Server Name: A Debian Server (with OS Debian Base)
-      lala.txt of 34 bytes
-      test.txt of 1232 bytes
-      testDir/ of 0 bytes
-    $ 
-
-Error handling
-^^^^^^^^^^^^^^
-
-The kamaki.clients standard error is ClientError. A ClientError is raised for any kind of kamaki.clients errors (errors reported by servers, type errors in arguments, etc.).
-
-A ClientError contains::
-
-    message     The error message.
-    status      An optional error code, e.g. after a server error.
-    details     Optional list of messages with error details.
-
-The following example concatenates examples 1.1 and 1.2 plus error handling
-
-.. code-block:: python
-
-    Example 1.3: Error handling
-
-
-    from kamaki.clients.cyclades import CycladesClient
-    from kamaki.clients.pithos import PithosClient
-
-    try:
-        my_cyclades_client = CycladesClient(base_url, token)
-    except ClientError:
-        print('Failed to initialize Cyclades client')
-
-    try:
-        my_pithos_client = PithosClient(base_url, token, account, container)
-    except ClientError:
-        print('Failed to initialize Pithos+ client')
-
-    try:
-        srv = my_cyclades_client.get_server_info(server_id)
-        print("Server Name: %s (with OS %s" % (srv['name'], srv['os']))
-
-        obj_list = my_pithos_client.list_objects(mycont)
-        for obj in obj_list:
-            print('  %s of %s bytes' % (obj['name'], obj['bytes']))
-    except ClientError as e:
-        print('Error: %s' % e)
-        if e.status:
-            print('- error code: %s' % e.status)
-        if e.details:
-            for detail in e.details:
-                print('- %s' % detail)
-
-Adding Commands
----------------
-
-Architecture
-^^^^^^^^^^^^
-
-Kamaki commands are implemented as python classes, decorated with a special decorator called *command*. This decorator is a method of kamaki.cli that adds a new command in a CommandTree structure (kamaki.cli.commant_tree). The later is used by interfaces to manage kamaki commands.
-
-The CommandTree structure
-"""""""""""""""""""""""""
-
-CommandTree manages a command by its path. Each command is stored in multiple nodes on the tree, so that the last term is a leaf and the route from root to that leaf represents the command path. For example the commands *store upload*, *store list* and *store info* are stored together as shown bellow::
-
-    - store
-    ''''''''|- info
-            |- list
-            |- upload
-
-Each command group should be stored on a different CommandTree. For that reason, command specification modules should contain a list of CommandTree objects, named *_commands*
-
-A command group information (name, description) is provided at CommandTree structure initialization::
-
-    _mygrp_commands = CommandTree('mygrp', 'My Group Commands')
-
-The command decorator
-"""""""""""""""""""""
-
-The *command* decorator mines all the information necessary to build a command specification which is inserted in a CommanTree instance::
-
-    class code  --->  command()  -->  updated CommandTree structure
-
-Kamaki interfaces make use of this CommandTree structure. Optimizations are possible by using special parameters on the command decorator method.
-
-.. code-block:: python
-
-    def command(cmd_tree, prefix='', descedants_depth=None):
-    """Load a class as a command
-        @cmd_tree is the CommandTree to be updated with a new command
-        @prefix of the commands allowed to be inserted ('' for all)
-        @descedants_depth is the depth of the tree descedants of the
-            prefix command.
-    """
-
-Creating a new command specification set
-""""""""""""""""""""""""""""""""""""""""
-
-A command specification developer should create a new module (python file) with as many classes as the command specifications to be offered. Each class should be decorated with *command*.
-
-A list of CommandTree structures must exist in the module scope, with the name _commands. Different CommandTree objects correspond to different command groups.
-
-Declare run-time argument
-"""""""""""""""""""""""""
-
-The argument mechanism allows the definition of run-time arguments. Some basic argument types are defined at the `argument module <cli.html#module-kamaki.cli.argument>`_, but it is not uncommon to extent these classes in order to achieve specialized type checking and syntax control (e.g. at `pithos_cli module <cli.html#module-kamaki.cli.commands.pithos_cli>`_).
-
-Putting them all together
-"""""""""""""""""""""""""
-
-The information that can be mined by *command* for each individual command are presented in the following, for a sample command:
-
-.. code-block:: python
-
-    @command(_mygrp_commands)
-    class mygrp_cmd1_cmd2(object):
-        """Command Description"""
-
-        def __init__(self, arguments={}):
-            arguments['arg1'] = FlagArgument(
-                'Run with arg1 on',
-                --arg1',
-                False)
-
-            self.arguments = arguments
-
-        def main(self, obligatory_param1, obligatory_param2,
-            optional_param1=None, optional_param2=None):
-            ...
-            command code here
-            ...
-
-This will load the following information on the CommandTree::
-
-    Syntax: mygrp cmd1 cmd2 <objigatory param1> <obligatory param2>
-        [optional_param1] [optional_param2] [--arg1]
-    Description: Command Description
-    Arguments help: --arg1: Run with arg1 on
-
-Letting kamaki know
-"""""""""""""""""""
-
-Kamaki will load a command specification *only* if it is set as a configurable option. To demonstrate this, let a commands module grps be implemented in the file *grps.py* where there are two command groups defined: mygrp1 mygrp2.
-
-Move grps.py to kamaki/cli/commands, the default place for command specifications
-
-In the configuration file, add:
-
-.. code-block:: console
-
-    [mygrp1]
-    cli=grps
-
-    [mygrp2]
-    cli=grps
-
-or alternatively
-
-.. code-block:: console
-
-    $ kamaki config set mygrp1.cli = grps
-    $ kamaki config set mygrp2.cli = grps
-
-Command modules don't have to live in kamaki/cli/commands, although this is suggested for uniformity. If a command module exist in another path::
-
-    [mygrp]
-    cli=/another/path/grps.py
-
-Developing a Command Specification Set
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-TODO
-
-Extending kamaki.clients
-------------------------
-
-By default, kamaki clients are REST clients (they manage HTTP requests and responses to communicate with services). This is achieved by importing the connection module, which is an httplib rapper.
-
-Connection
-^^^^^^^^^^
-
-The connection module features an error handling and logging system, a lazy response mechanism, a pooling mechanism as well as concurrency control for thread-demanding client functions (e.g. store upload).
-
-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.
-
-.. code-block:: python
-    
-    #  ${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
-
-.. code-block:: python
-
-    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)
-
-How to use your client
-^^^^^^^^^^^^^^^^^^^^^^
-
-External applications must instantiate a MyNewClient object.
-
-.. code-block:: python
-
-    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)
-    ...
-
-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
-
-.. code-block:: python
-
-    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)
-
-The CLI API
------------
-
-.. toctree::
-
-    cli
-
-.. _the-client-api-ref:
-
-The clients API
----------------
-
-Imports
-^^^^^^^
-
-.. toctree::
-    connection
-
-Modules list
-^^^^^^^^^^^^
-
-compute
-^^^^^^^
-
-.. automodule:: kamaki.clients.compute
-    :members:
-    :show-inheritance:
-    :undoc-members:
-
-
-cyclades
-^^^^^^^^
-
-.. automodule:: kamaki.clients.cyclades
-    :members:
-    :show-inheritance:
-    :undoc-members:
-
-
-storage
-^^^^^^^
-
-.. automodule:: kamaki.clients.storage
-    :members:
-    :show-inheritance:
-    :undoc-members:
-
-
-pithos
-^^^^^^
-
-.. automodule:: kamaki.clients.pithos
-    :members:
-    :show-inheritance:
-    :undoc-members:
-
-pithos_rest_api
-^^^^^^^^^^^^^^^
-
-.. automodule:: kamaki.clients.pithos_rest_api
-    :members:
-    :show-inheritance:
-    :undoc-members:
-
-
-image
-^^^^^
-
-.. automodule:: kamaki.clients.image
-    :members:
-    :show-inheritance:
-    :undoc-members:
-
-
-astakos
-^^^^^^^
-
-.. automodule:: kamaki.clients.astakos
-    :members:
-    :show-inheritance:
-    :undoc-members:
-
-
-utils
-^^^^^
-
-.. automodule:: kamaki.clients.utils
-    :members:
-    :show-inheritance:
-    :undoc-members:
diff --git a/docs/developers/adding-commands.rst b/docs/developers/adding-commands.rst
new file mode 100644 (file)
index 0000000..e2dd13c
--- /dev/null
@@ -0,0 +1,302 @@
+Adding Commands
+===============
+
+Kamaki commands are implemented as python classes, decorated with a special decorator called *command*. This decorator is a method of kamaki.cli that adds a new command in a CommandTree structure (kamaki.cli.commant_tree). The later is used by interfaces to manage kamaki commands.
+
+In the following, a set of kamaki commands will be implemented::
+
+    mygrp1 list all                         //show a list
+    mygrp1 list details [--match=<>]        //show list of details
+    mygrp2 list all [regular expression] [-l]       //list all subjects
+    mygrp2 info <id> [name]      //get information for subject with id
+
+There are two command sets to implement, namely mygrp1 and mygrp2. The first will contain two commands, namely list-all and list-details. The second one will also contain two commands, list-all and info. To avoid ambiguities, command names should rather be prefixed with the group they belong to, e.g. mygrp1-list-all and mygrp2-list-all.
+
+The first command has the simplest possible syntax: no parameters, no runtime arguments. The second accepts an optional runtime argument with a value. The third features an optional argument and an optional runtime flag argument. The last is an example of a command with an obligatory and an optional argument.
+
+Samples of the expected behavior in one-command mode are following:
+
+.. code-block:: console
+
+    $kamaki mygrp1
+        mygrp1 description
+
+        Options
+         - - - -
+        list
+    $ kamaki mygrp1 list
+        Syntax Error
+
+        Options
+         - - - -
+        all        show a list
+        details     show a list of details
+    $ kamaki mygrp1 list all
+        ... (mygrp1 client method is called) ...
+    $ kamaki mygrp2 list all 'Z[.]' -l
+        ... (mygrp2 client method is called) ...
+    $
+
+The above example will be used throughout the present guide for clarification purposes.
+
+The CommandTree structure
+-------------------------
+
+CommandTree manages a command by its path. Each command is stored in multiple nodes on the tree, so that the last term is a leaf and the route from root to that leaf represents the command path. For example the commands *store upload*, *store list* and *store info* are stored together as shown bellow::
+
+    - store
+    ''''''''|- info
+            |- list
+            |- upload
+
+The example used in the present, should result to the creation of two trees::
+
+    - mygrp1
+    ''''''''|- list
+            '''''''|- all
+                   |- details
+
+    - mygrp2
+    ''''''''|- list
+            '''''''|- all
+            |- info
+
+Each command group should be stored on a different CommandTree. For that reason, command specification modules should contain a list of CommandTree objects, named *_commands*
+
+A command group information (name, description) is provided at CommandTree structure initialization:
+
+.. code-block:: python
+
+    _mygrp1_commands = CommandTree('mygrp', 'mygrp1 description')
+    _mygrp2_commands = CommandTree('mygrp', 'mygrp2 description')
+
+    _commands = [_mygrp1_commands, _mygrp2_commands]
+
+The command decorator
+---------------------
+
+The *command* decorator mines all the information necessary to build a command specification which is then inserted in a CommanTree instance::
+
+    class code  --->  command()  -->  updated CommandTree structure
+
+Kamaki interfaces make use of this CommandTree structure. Optimizations are possible by using special parameters on the command decorator method.
+
+.. code-block:: python
+
+    def command(cmd_tree, prefix='', descedants_depth=None):
+    """Load a class as a command
+        @cmd_tree is the CommandTree to be updated with a new command
+        @prefix of the commands allowed to be inserted ('' for all)
+        @descedants_depth is the depth of the tree descedants of the
+            prefix command.
+    """
+
+Creating a new command specification set
+----------------------------------------
+
+A command specification developer should create a new module (python file) with as many classes as the command specifications to be offered. Each class should be decorated with *command*.
+
+.. code-block:: python
+
+    ...
+    _commands = [_mygrp1_commands, _mygrp2_commands]
+
+    @command(_mygrp1_commands)
+    class mygrp1_list_all():
+        ...
+
+    ...
+
+A list of CommandTree structures must exist in the module scope, with the name _commands, as shown above. Different CommandTree objects correspond to different command groups.
+
+Get command description
+-----------------------
+
+The description of each command is the first line of the class commend. The following declaration of *mygrp2-info* command has a "*get information for subject with id*" description.
+
+.. code-block:: python
+
+    ...
+    @command(_mygrp2_commands)
+    class mygrp2_info()
+        """get information for subject with id"""
+        ...
+
+Declare run-time argument
+-------------------------
+
+The argument mechanism allows the definition of run-time arguments. Some basic argument types are defined at the `argument module <cli.html#module-kamaki.cli.argument>`_, but it is not uncommon to extent these classes in order to achieve specialized type checking and syntax control (e.g. at `pithos_cli module <cli.html#module-kamaki.cli.commands.pithos_cli>`_).
+
+To declare a run-time argument on a specific command, the object class should initialize a dict called *arguments* , where Argument objects are stored. Each argument object is a possible run-time argument. Syntax checking happens at client level, while the type checking is implemented in the Argument code (thus, many different Argument types might be needed).
+
+.. code-block:: python
+
+    from kamaki.cli.argument import ValueArgument
+    ...
+
+    @command(_mygrp1_commands)
+    class mygrp1_list_details():
+        """list of details"""
+
+        def __init__(self, global_args={})
+            global_args['match'] = ValueArgument(
+                'Filter results to match string',
+                '--match')
+            self.arguments = global_args
+
+The main method and command parameters
+--------------------------------------
+
+The command behavior for each command / class is coded in *main*. The parameters of *main* method defines the command parameters part of the syntax. In specific::
+
+    main(self, param)                   - obligatory parameter
+    main(self, param=None)              - optional parameter
+    main(self, param1, param2=42)       - <param1> [param2]
+    main(self, param1____param2)        - <param1:param2>
+    main(self, param1____param2=[])     - [param1:param2]
+    main(self, param1____param2__)      - <param1[:param2]>
+    main(self, param1____param2__='')   - [param1[:param2]]
+    main(self, *args)                   - arbitary number of params [...]
+    main(self, param1____param2, *args) - <param1:param2> [...]
+
+The information that can be mined by *command* for each individual command is presented in the following:
+
+.. code-block:: python
+    :linenos:
+
+    from kamaki.cli.argument import FlagArgument
+    ...
+
+    _commands = [_mygrp1_commands, _mygrp2=commands]
+    ...
+
+    @command(_mygrp2_commands)
+    class mygrp2_list_all(object):
+        """List all subjects"""
+
+        def __init__(self, global_args={}):
+            global_args['list'] = FlagArgument(
+                'detailed list',
+                '-l,
+                False)
+
+            self.arguments = global_args
+
+        def main(self, reg_exp=None):
+            ...
+
+This will load the following information on the CommandTree:
+
+* Syntax (from lines 8,12,19): mygrp list all [reg exp] [-l]
+* Description (form line 9): List all subjects
+* Arguments help (from line 13,14): -l: detailed list
+
+Letting kamaki know
+-------------------
+
+Kamaki will load a command specification *only* if it is set as a configurable option. To demonstrate this, let the command specifications coded above be stored in a file named *grps.py*.
+
+The developer should move file *grps.py* to kamaki/cli/commands, the default place for command specifications, although running a command specification from a different path is also a kamaki feature.
+
+The user has to use a configuration file where the following is added:
+::
+
+    [mygrp1]
+    cli=grps
+
+    [mygrp2]
+    cli=grps
+
+or alternatively:
+
+.. code-block:: console
+
+    $ kamaki config set mygrp1.cli = grps
+    $ kamaki config set mygrp2.cli = grps
+
+Command specification modules don't need to live in kamaki/cli/commands, although this is suggested for uniformity. If a command module exist in another path::
+
+    [mygrp]
+    cli=/another/path/grps.py
+
+Summary: create a command set
+-----------------------------
+
+.. code-block:: python
+
+    #  File: grps.py
+
+    from kamaki.cli.command_tree import CommandTree
+    from kamaki.cli.argument import ValueArgument, FlagArgument
+    ...
+
+
+    #  Initiallize command trees
+
+    _mygrp1_commands = CommandTree('mygrp', 'mygrp1 description')
+    _mygrp2_commands = CommandTree('mygrp', 'mygrp2 description')
+
+    _commands = [_mygrp1_commands, _mygrp2_commands]
+
+
+    #  Define command specifications
+
+
+    @command(_mygrp1_commands)
+    class mygrp1_list_all():
+        """show a list"""
+
+        arguments = {}
+
+        def main(self):
+            ...
+
+
+    @command(_mygrp1_commands)
+    class mygrp1_list_details():
+        """show list of details"""
+
+        arguments = {}
+
+        def __init__(self, global_args={})
+            global_args['match'] = ValueArgument(
+                'Filter results to match string',
+                '--match')
+            self.arguments = global_args
+
+        def main(self):
+            ...
+            match_value = self.arguments['list'].value
+            ...
+
+
+    @command(_mygrp2_commands)
+    class mygrp2_list_all():
+        """list all subjects"""
+
+        arguments = {}
+
+        def __init__(self, global_args={})
+            global_args['match'] = FlagArgument('detailed listing', '-l')
+            self.arguments = global_args
+
+        def main(self, regular_expression=None):
+            ...
+            detail_flag = self.arguments['list'].value
+            ...
+            if detail_flag:
+                ...
+            ...
+            if regular_expression:
+                ...
+            ...
+
+
+    @command(_mygrp2_commands)
+    class mygrp2_info():
+        """get information for subject with id"""
+
+        arguments = {}
+
+        def main(self, id, name=''):
+            ...
diff --git a/docs/developers/clients-api.rst b/docs/developers/clients-api.rst
new file mode 100644 (file)
index 0000000..82c9811
--- /dev/null
@@ -0,0 +1,109 @@
+Creating applications with kamaki API
+=====================================
+
+
+Kamaki features a clients API for building third-party client applications that communicate with OpenStack and / or Synnefo cloud services. The package is called kamaki.clients and contains a number of 
+
+A good example of an application build on kamaki.clients is kamaki.cli, the command line interface of kamaki. 
+
+Since synnefo services are build as OpenStack extensions, an inheritance approach has been chosen for implementing clients for both. In specific, the *compute*, *storage* and *image* modules are clients of the OS compute, OS storage and Glance APIs, respectively. On the contrary, all the other modules are Synnefo extensions (*cyclades* extents *compute*, *pithos* and *pithos_rest_api* extent *storage*) or novel synnefo services (e.g. *astakos*).
+
+Setup a client instance
+-----------------------
+
+External applications may instantiate one or more kamaki clients.
+
+.. code-block:: python
+    :emphasize-lines: 1
+
+    Example 1.1: Instantiate a Cyclades client
+
+
+    from kamaki.clients.cyclades import CycladesClient
+    from kamaki.clients.pithos import PithosClient
+
+    my_cyclades_client = CycladesClient(base_url, token)
+    my_pithos_client = PithosClient(base_url, token, account, container)
+
+.. note:: *cyclades* and *pithos* clients inherit all methods of *compute* and *storage* clients respectively. Separate compute or storage objects should be used only when implementing applications for strict OS Compute or OS Storage services.
+
+Use client methods
+------------------
+
+Client methods can now be called. Developers are advised to consult :ref:`the-client-api-ref` for details on the available methods and how to use them.
+
+In the following example, the *cyclades* and *pithos* clients of example 1.1 are used to extract some information, that is then printed to the standard output.
+
+
+.. code-block:: python
+    :emphasize-lines: 1,2
+
+    Example 1.2: Print server name and OS for server with server_id
+                Print objects in container mycont
+
+
+    srv = my_cyclades_client.get_server_info(server_id)
+    print("Server Name: %s (with OS %s" % (srv['name'], srv['os']))
+
+    obj_list = my_pithos_client.list_objects(mycont)
+    for obj in obj_list:
+        print('  %s of %s bytes' % (obj['name'], obj['bytes']))
+
+.. code-block:: console
+    :emphasize-lines: 1
+
+    Run of examples 1.1 + 1.2
+
+
+    $ python test_script.py
+    Server Name: A Debian Server (with OS Debian Base)
+      lala.txt of 34 bytes
+      test.txt of 1232 bytes
+      testDir/ of 0 bytes
+    $ 
+
+Error handling
+--------------
+
+The kamaki.clients standard error is ClientError. A ClientError is raised for any kind of kamaki.clients errors (errors reported by servers, type errors in arguments, etc.).
+
+A ClientError contains::
+
+    message     The error message.
+    status      An optional error code, e.g. after a server error.
+    details     Optional list of messages with error details.
+
+The following example concatenates examples 1.1 and 1.2 plus error handling
+
+.. code-block:: python
+
+    Example 1.3: Error handling
+
+
+    from kamaki.clients.cyclades import CycladesClient
+    from kamaki.clients.pithos import PithosClient
+
+    try:
+        my_cyclades_client = CycladesClient(base_url, token)
+    except ClientError:
+        print('Failed to initialize Cyclades client')
+
+    try:
+        my_pithos_client = PithosClient(base_url, token, account, container)
+    except ClientError:
+        print('Failed to initialize Pithos+ client')
+
+    try:
+        srv = my_cyclades_client.get_server_info(server_id)
+        print("Server Name: %s (with OS %s" % (srv['name'], srv['os']))
+
+        obj_list = my_pithos_client.list_objects(mycont)
+        for obj in obj_list:
+            print('  %s of %s bytes' % (obj['name'], obj['bytes']))
+    except ClientError as e:
+        print('Error: %s' % e)
+        if e.status:
+            print('- error code: %s' % e.status)
+        if e.details:
+            for detail in e.details:
+                print('- %s' % detail)
similarity index 51%
rename from docs/cli.rst
rename to docs/developers/code.rst
index 288f3e2..2d7c7a7 100644 (file)
@@ -1,56 +1,61 @@
+APIs code
+=========
+
+.. the-cli-api-ref
+
 Command Specifications
-======================
+----------------------
 
 astakos
--------
+^^^^^^^
 
 .. automodule:: kamaki.cli.commands.astakos_cli
     :members:
     :undoc-members:
 
 cyclades (server, flavor, network)
-----------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 .. automodule:: kamaki.cli.commands.cyclades_cli
     :members:
     :undoc-members:
 
 pithos (store)
---------------
+^^^^^^^^^^^^^^
 
 .. automodule:: kamaki.cli.commands.pithos_cli
     :members:
     :undoc-members:
 
 image
------
+^^^^^
 
 .. automodule:: kamaki.cli.commands.image_cli
     :members:
     :undoc-members:
 
 Kamaki commands
----------------
+^^^^^^^^^^^^^^^
 
 config
-^^^^^^
+""""""
 
 .. automodule:: kamaki.cli.commands.config_cli
     :members:
     :undoc-members:
 
 history
-^^^^^^^
+"""""""
 
 .. automodule:: kamaki.cli.commands.history_cli
     :members:
     :undoc-members:
 
-Command Line Interface Code
-===========================
+Command Line Interfaces
+-----------------------
 
 argument
---------
+^^^^^^^^
 
 .. automodule:: kamaki.cli.argument
     :members:
@@ -59,7 +64,7 @@ argument
 
 
 command_shell
--------------
+^^^^^^^^^^^^^
 
 .. automodule:: kamaki.cli.command_shell
     :members:
@@ -68,7 +73,7 @@ command_shell
 
 
 command_tree
-------------
+^^^^^^^^^^^^
 
 .. automodule:: kamaki.cli.command_tree
     :members:
@@ -77,7 +82,7 @@ command_tree
 
 
 config
-------
+^^^^^^
 
 .. automodule:: kamaki.cli.config
     :members:
@@ -85,7 +90,7 @@ config
     :undoc-members:
 
 errors
-------
+^^^^^^
 
 .. automodule:: kamaki.cli.errors
     :members:
@@ -94,7 +99,7 @@ errors
 
 
 history
--------
+^^^^^^^
 
 .. automodule:: kamaki.cli.history
     :members:
@@ -103,9 +108,94 @@ history
 
 
 utils
------
+^^^^^
 
 .. automodule:: kamaki.cli.utils
     :members:
     :show-inheritance:
     :undoc-members:
+
+
+.. _the-client-api-ref:
+
+The clients API
+---------------
+
+Imports
+^^^^^^^
+
+.. toctree::
+    connection
+
+Modules list
+^^^^^^^^^^^^
+
+compute
+^^^^^^^
+
+.. automodule:: kamaki.clients.compute
+    :members:
+    :show-inheritance:
+    :undoc-members:
+
+
+cyclades
+^^^^^^^^
+
+.. automodule:: kamaki.clients.cyclades
+    :members:
+    :show-inheritance:
+    :undoc-members:
+
+
+storage
+^^^^^^^
+
+.. automodule:: kamaki.clients.storage
+    :members:
+    :show-inheritance:
+    :undoc-members:
+
+
+pithos
+^^^^^^
+
+.. automodule:: kamaki.clients.pithos
+    :members:
+    :show-inheritance:
+    :undoc-members:
+
+pithos_rest_api
+^^^^^^^^^^^^^^^
+
+.. automodule:: kamaki.clients.pithos_rest_api
+    :members:
+    :show-inheritance:
+    :undoc-members:
+
+
+image
+^^^^^
+
+.. automodule:: kamaki.clients.image
+    :members:
+    :show-inheritance:
+    :undoc-members:
+
+
+astakos
+^^^^^^^
+
+.. automodule:: kamaki.clients.astakos
+    :members:
+    :show-inheritance:
+    :undoc-members:
+
+
+utils
+^^^^^
+
+.. automodule:: kamaki.clients.utils
+    :members:
+    :show-inheritance:
+    :undoc-members:
diff --git a/docs/developers/extending-clients-api.rst b/docs/developers/extending-clients-api.rst
new file mode 100644 (file)
index 0000000..0e1691f
--- /dev/null
@@ -0,0 +1,97 @@
+Extending kamaki.clients
+========================
+
+By default, kamaki clients are REST clients (they manage HTTP requests and responses to communicate with services). This is achieved by importing the connection module, which is an httplib rapper.
+
+Connection
+----------
+
+The connection module features an error handling and logging system, a lazy response mechanism, a pooling mechanism as well as concurrency control for thread-demanding client functions (e.g. store upload).
+
+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.
+
+.. code-block:: python
+    
+    #  ${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
+
+.. code-block:: python
+
+    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)
+
+How to use your client
+----------------------
+
+External applications must instantiate a MyNewClient object.
+
+.. code-block:: python
+
+    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)
+    ...
+
+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
+
+.. code-block:: python
+
+    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)
\ No newline at end of file
diff --git a/docs/devguide.rst b/docs/devguide.rst
new file mode 100644 (file)
index 0000000..d2fe571
--- /dev/null
@@ -0,0 +1,11 @@
+Developers Guide
+================
+
+.. toctree::
+    :numbered:
+    :glob:
+
+    developers/clients-api
+    developers/adding-commands
+    developers/extending-clients-api
+    developers/code
\ No newline at end of file
index dd27329..9a4fe51 100644 (file)
@@ -22,13 +22,15 @@ Contents:
 
 .. toctree::
    :maxdepth: 2
+   :numbered:
+   :glob:
    
    overview
    installation
    setup
    usage
    commands
-   developers
+   devguide
 
 
 Indices and tables
@@ -37,4 +39,3 @@ Indices and tables
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
-
index 09cb261..cc61712 100644 (file)
@@ -133,4 +133,4 @@ Kamaki can be installed on Mac OS X systems from source, by following the steps
 Windows
 -------
 
-It has proven possible (and not too tricky) to install kamaki on Windows console using some auxiliary applications, but Windows users are not the target audience for the time being.
+Although it is proven not too tricky to install kamaki on Windows console using `git for windows <http://git-scm.com/downloads>`_, Windows environments are not supported at the time being.
index 6749f6b..751da94 100644 (file)
@@ -44,13 +44,20 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ HEAD at account level
+
         --- request parameters ---
-        @param until (string): optional timestamp
-        --- --- optional request headers ---
-        @param if_modified_since (string): Retrieve if account has changed
-        since provided timestamp
-        @param if_unmodified_since (string): Retrieve if account has not
-        change since provided timestamp
+
+        :param until: (string) optional timestamp
+
+        --- request headers ---
+
+        :param if_modified_since: (string) Retrieve if account has changed
+            since provided timestamp
+
+        :param if_unmodified_since: (string) Retrieve if account has not
+            change since provided timestamp
+
+        :returns: ConnectionResponse
         """
 
         self.assert_account()
@@ -74,21 +81,32 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """  Full Pithos+ GET at account level
+
         --- request parameters ---
-        @param limit (integer): The amount of results requested
-        (server will use default value if None)
-        @param marker (string): Return containers with name
-        lexicographically after marker
-        @param format (string): reply format can be json or xml
-        (default: json)
-        @param shared (bool): If true, only shared containers will be
-        included in results
-        @param until (string): optional timestamp
-        --- --- optional request headers ---
-        @param if_modified_since (string): Retrieve if account has changed
-        since provided timestamp
-        @param if_unmodified_since (string): Retrieve if account has not
-        changed since provided timestamp
+
+        :param limit: (integer) The amount of results requested
+            (server will use default value if None)
+
+        :param marker: string Return containers with name
+            lexicographically after marker
+
+        :param format: (string) reply format can be json or xml
+            (default: json)
+
+        :param shared: (bool) If true, only shared containers will be
+            included in results
+
+        :param until: (string) optional timestamp
+
+        --- request headers ---
+
+        :param if_modified_since: (string) Retrieve if account has changed
+            since provided timestamp
+
+        :param if_unmodified_since: (string) Retrieve if account has not
+            changed since provided timestamp
+
+        :returns: ConnectionResponse
         """
 
         self.assert_account()
@@ -115,17 +133,26 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ POST at account level
+
         --- request parameters ---
-        @param update (bool): if True, Do not replace metadata/groups
+
+        :param update: (bool) if True, Do not replace metadata/groups
+
         --- request headers ---
-        @groups (dict): Optional user defined groups in the form
-        { 'group1':['user1', 'user2', ...],
-        'group2':['userA', 'userB', ...], }
-        @metadata (dict): Optional user defined metadata in the form
-        { 'name1': 'value1', 'name2': 'value2', ... }
-        @param quota(integer): If supported, sets the Account quota
-        @param versioning(string): If supported, sets the Account versioning
-        to 'auto' or some other supported versioning string
+
+        :param groups: (dict) Optional user defined groups in the form
+            { 'group1':['user1', 'user2', ...],
+            'group2':['userA', 'userB', ...], }
+
+        :param metadata: (dict) Optional user defined metadata in the form
+            { 'name1': 'value1', 'name2': 'value2', ... }
+
+        :param quota: (integer) If supported, sets the Account quota
+
+        :param versioning: (string) If supported, sets the Account versioning
+            to 'auto' or some other supported versioning string
+
+        :returns: ConnectionResponse
         """
 
         self.assert_account()
@@ -152,13 +179,20 @@ class PithosRestAPI(StorageClient):
     def container_head(self, until=None,
         if_modified_since=None, if_unmodified_since=None, *args, **kwargs):
         """ Full Pithos+ HEAD at container level
+
         --- request params ---
-        @param until (string): optional timestamp
-        --- optional request headers ---
-        @param if_modified_since (string): Retrieve if account has changed
-        since provided timestamp
-        @param if_unmodified_since (string): Retrieve if account has not
-        changed since provided timestamp
+
+        :param until: (string) optional timestamp
+
+        --- request headers ---
+
+        :param if_modified_since: (string) Retrieve if account has changed
+            since provided timestamp
+
+        :param if_unmodified_since: (string) Retrieve if account has not
+            changed since provided timestamp
+
+        :returns: ConnectionResponse
         """
 
         self.assert_container()
@@ -187,28 +221,43 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ GET at container level
+
         --- request parameters ---
-        @param limit (integer): The amount of results requested
-        (server qill use default value if None)
-        @param marker (string): Return containers with name lexicographically
-        after marker
-        @param prefix (string): Return objects starting with prefix
-        @param delimiter (string): Return objects up to the delimiter
-        @param path (string): assume prefix = path and delimiter = /
-        (overwrites prefix and delimiter)
-        @param format (string): reply format can be json or xml (default:json)
-        @param meta (list): Return objects that satisfy the key queries in
-        the specified comma separated list (use <key>, !<key> for
-        existence queries, <key><op><value> for value queries, where <op>
-        can be one of =, !=, <=, >=, <, >)
-        @param shared (bool): If true, only shared containers will be included
-        in results
-        @param until (string): optional timestamp
-        --- --- optional request headers ---
-        @param if_modified_since (string): Retrieve if account has changed
-        since provided timestamp
-        @param if_unmodified_since (string): Retrieve if account has not
-        changed since provided timestamp
+
+        :param limit: (integer) The amount of results requested
+            (server will use default value if None)
+
+        :param marker: (string) Return containers with name lexicographically
+            after marker
+
+        :param prefix: (string) Return objects starting with prefix
+
+        :param delimiter: (string) Return objects up to the delimiter
+
+        :param path: (string) assume prefix = path and delimiter = /
+            (overwrites prefix and delimiter)
+
+        :param format: (string) reply format can be json or xml (default:json)
+
+        :param meta: (list) Return objects that satisfy the key queries in
+            the specified comma separated list (use <key>, !<key> for
+            existence queries, <key><op><value> for value queries, where <op>
+            can be one of =, !=, <=, >=, <, >)
+
+        :param shared: (bool) If true, only shared containers will be included
+            in results
+
+        :param until: (string) optional timestamp
+
+        --- request headers ---
+
+        :param if_modified_since: (string) Retrieve if account has changed
+            since provided timestamp
+
+        :param if_unmodified_since: (string) Retrieve if account has not
+            changed since provided timestamp
+
+        :returns: ConnectionResponse
         """
 
         self.assert_container()
@@ -241,13 +290,17 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ PUT at container level
+
         --- request headers ---
-        @param quota (integer): Size limit in KB
-        @param versioning (string): 'auto' or other string supported by server
-        @metadata (dict): Optional user defined metadata in the form
-        {   'name1': 'value1',
-        'name2': 'value2', ...
-        }
+
+        :param quota: (integer) Size limit in KB
+
+        :param versioning: (string) 'auto' or other string supported by server
+
+        :param metadata: (dict) Optional user defined metadata in the form
+            { 'name1': 'value1', 'name2': 'value2', ... }
+
+        :returns: ConnectionResponse
         """
         self.assert_container()
 
@@ -273,19 +326,29 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ POST at container level
+
         --- request params ---
-        @param update (bool):  if True, Do not replace metadata/groups
-        @param format(string): json (default) or xml
+
+        :param update: (bool)  if True, Do not replace metadata/groups
+
+        :param format: (string) json (default) or xml
+
         --- request headers ---
-        @param quota (integer): Size limit in KB
-        @param versioning (string): 'auto' or other string supported by server
-        @metadata (dict): Optional user defined metadata in the form
-        {   'name1': 'value1',
-        'name2': 'value2', ...
-        }
-        @param content_type (string): set a custom content type
-        @param content_length (string): set a custrom content length
-        @param transfer_encoding (string): set a custrom transfer encoding
+
+        :param quota: (integer) Size limit in KB
+
+        :param versioning: (string) 'auto' or other string supported by server
+
+        :param metadata: (dict) Optional user defined metadata in the form
+            { 'name1': 'value1', 'name2': 'value2', ... }
+
+        :param content_type: (string) set a custom content type
+
+        :param content_length: (string) set a custrom content length
+
+        :param transfer_encoding: (string) set a custrom transfer encoding
+
+        :returns: ConnectionResponse
         """
         self.assert_container()
 
@@ -307,9 +370,13 @@ class PithosRestAPI(StorageClient):
 
     def container_delete(self, until=None, delimiter=None, *args, **kwargs):
         """ Full Pithos+ DELETE at container level
+
         --- request parameters ---
-        @param until (timestamp string): if defined, container is purged up to
-        that time
+
+        :param until: (timestamp string) if defined, container is purged up to
+            that time
+
+        :returns: ConnectionResponse
         """
 
         self.assert_container()
@@ -330,17 +397,26 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ HEAD at object level
+
         --- request parameters ---
-        @param version (string): optional version identified
+
+        :param version: (string) optional version identified
+
         --- request headers ---
-        @param if_etag_match (string): if provided, return only results
-        with etag matching with this
-        @param if_etag_not_match (string): if provided, return only results
-        with etag not matching with this
-        @param if_modified_since (string): Retrieve if account has changed
-        since provided timestamp
-        @param if_unmodified_since (string): Retrieve if account has not
-        changed since provided timestamp
+
+        :param if_etag_match: (string) if provided, return only results
+            with etag matching with this
+
+        :param if_etag_not_match: (string) if provided, return only results
+            with etag not matching with this
+
+        :param if_modified_since: (string) Retrieve if account has changed
+            since provided timestamp
+
+        :param if_unmodified_since: (string) Retrieve if account has not
+            changed since provided timestamp
+
+        :returns: ConnectionResponse
         """
 
         self.assert_container()
@@ -369,21 +445,34 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ GET at object level
+
         --- request parameters ---
-        @param format (string): json (default) or xml
-        @param hashmap (bool): Optional request for hashmap
-        @param version (string): optional version identified
+
+        :param format: (string) json (default) or xml
+
+        :param hashmap: (bool) Optional request for hashmap
+
+        :param version: (string) optional version identified
+
         --- request headers ---
-        @param data_range (string): Optional range of data to retrieve
-        @param if_range (bool):
-        @param if_etag_match (string): if provided, return only results
-        with etag matching with this
-        @param if_etag_not_match (string): if provided, return only results
-        with etag not matching with this
-        @param if_modified_since (string): Retrieve if account has changed
-        since provided timestamp
-        @param if_unmodified_since (string): Retrieve if account has not
-        changed since provided timestamp
+
+        :param data_range: (string) Optional range of data to retrieve
+
+        :param if_range: (bool)
+
+        :param if_etag_match: (string) if provided, return only results
+            with etag matching with this
+
+        :param if_etag_not_match: (string) if provided, return only results
+            with etag not matching with this
+
+        :param if_modified_since: (string) Retrieve if account has changed
+            since provided timestamp
+
+        :param if_unmodified_since: (string) Retrieve if account has not
+            changed since provided timestamp
+
+        :returns: ConnectionResponse
         """
 
         self.assert_container()
@@ -427,38 +516,60 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ PUT at object level
+
         --- request parameters ---
-        @param format (string): json (default) or xml
-        @param hashmap (bool): Optional hashmap provided instead of data
+
+        :param format: (string) json (default) or xml
+
+        :param hashmap: (bool) Optional hashmap provided instead of data
+
         --- request headers ---
-        @param if_etag_match (string): if provided, return only results
-        with etag matching with this
-        @param if_etag_not_match (string): if provided, return only results
-        with etag not matching with this
-        @param etag (string): The MD5 hash of the object (optional to check
-        written data)
-        @param content_length (integer): The size of the data written
-        @param content_type (string): The MIME content type of the object
-        @param transfer_encoding (string): Set to chunked to specify
-        incremental uploading (if used, Content-Length is ignored)
-        @param copy_from (string): The source path in the form
-        /<container>/<object>
-        @param move_from (string): The source path in the form
-        /<container>/<object>
-        @param source_account (string): The source account to copy/move from
-        @param source_version (string): The source version to copy from
-        @param conent_encoding (string): The encoding of the object
-        @param content_disposition (string): Presentation style of the object
-        @param manifest (string): Object parts prefix in
-        /<container>/<object> form
-        @param permissions (dict): Object permissions in the form (all fields
-        are optional)
-        { 'read':[user1, group1, user2, ...],
-        'write':['user3, group2, group3, ...] }
-        @param public (bool): If true, Object is publicly accessible,
-        if false, not
-        @param metadata (dict): Optional user defined metadata in the form
-        {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
+
+        :param if_etag_match: (string) if provided, return only results
+            with etag matching with this
+
+        :param if_etag_not_match: (string) if provided, return only results
+            with etag not matching with this
+
+        :param etag: (string) The MD5 hash of the object (optional to check
+            written data)
+
+        :param content_length: (integer) The size of the data written
+
+        :param content_type: (string) The MIME content type of the object
+
+        :param transfer_encoding: (string) Set to chunked to specify
+            incremental uploading (if used, Content-Length is ignored)
+
+        :param copy_from: (string) The source path in the form
+            /<container>/<object>
+
+        :param move_from: (string) The source path in the form
+            /<container>/<object>
+
+        :param source_account: (string) The source account to copy/move from
+
+        :param source_version: (string) The source version to copy from
+
+        :param conent_encoding: (string) The encoding of the object
+
+        :param content_disposition: (string) Presentation style of the object
+
+        :param manifest: (string) Object parts prefix in
+            /<container>/<object> form
+
+        :param permissions: (dict) Object permissions in the form (all fields
+            are optional)
+            { 'read':[user1, group1, user2, ...],
+            'write':['user3, group2, group3, ...] }
+
+        :param public: (bool) If true, Object is publicly accessible,
+            if false, not
+
+        :param metadata: (dict) Optional user defined metadata in the form
+            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
+
+        :returns: ConnectionResponse
         """
 
         self.assert_container()
@@ -517,31 +628,49 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ COPY at object level
+
         --- request parameters ---
-        @param format (string): json (default) or xml
-        @param ignore_content_type (bool): Ignore the supplied Content-Type
+
+        :param format: (string) json (default) or xml
+
+        :param ignore_content_type: (bool) Ignore the supplied Content-Type
+
         --- request headers ---
-        @param if_etag_match (string): if provided, copy only results
-        with etag matching with this
-        @param if_etag_not_match (string): if provided, copy only results
-        with etag not matching with this
-        @param destination (string): The destination path in the form
-        /<container>/<object>
-        @param destination_account (string): The destination account to copy to
-        @param content_type (string): The MIME content type of the object
-        @param content_encoding (string): The encoding of the object
-        @param content_disposition (string): Object resentation style
-        @param source_version (string): The source version to copy from
-        @param permissions (dict): Object permissions in the form
-        (all fields are optional)
-        { 'read':[user1, group1, user2, ...],
-        'write':['user3, group2, group3, ...] }
-        @permissions override source permissions, removing any old permissions
-        @param public (bool): If true, Object is publicly accessible
-        @param metadata (dict): Optional user defined metadata in the form
-        {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
-        Metadata are appended to the source metadata. In case of same keys,
-        they replace the old metadata
+
+        :param if_etag_match: (string) if provided, copy only results
+            with etag matching with this
+
+        :param if_etag_not_match: (string) if provided, copy only results
+            with etag not matching with this
+
+        :param destination: (string) The destination path in the form
+            /<container>/<object>
+
+        :param destination_account: (string) The destination account to copy to
+
+        :param content_type: (string) The MIME content type of the object
+
+        :param content_encoding: (string) The encoding of the object
+
+        :param content_disposition: (string) Object resentation style
+
+        :param source_version: (string) The source version to copy from
+
+        :param permissions: (dict) Object permissions in the form
+            (all fields are optional)
+            { 'read':[user1, group1, user2, ...],
+            'write':['user3, group2, group3, ...] }
+
+        :param permissions: update permissions
+
+        :param public: (bool) If true, Object is publicly accessible
+
+        :param metadata: (dict) Optional user defined metadata in the form
+            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
+            Metadata are appended to the source metadata. In case of same
+            keys, they replace the old metadata
+
+        :returns: ConnectionResponse
         """
 
         self.assert_container()
@@ -594,28 +723,45 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ COPY at object level
+
         --- request parameters ---
-        @param format (string): json (default) or xml
-        @param ignore_content_type (bool): Ignore the supplied Content-Type
+
+        :param format: (string) json (default) or xml
+
+        :param ignore_content_type: (bool) Ignore the supplied Content-Type
+
         --- request headers ---
-        @param if_etag_match (string): if provided, return only results
-        with etag matching with this
-        @param if_etag_not_match (string): if provided, return only results
-        with etag not matching with this
-        @param destination (string): The destination path in the form
-        /<container>/<object>
-        @param destination_account (string): The destination account to copy to
-        @param content_type (string): The MIME content type of the object
-        @param content_encoding (string): The encoding of the object
-        @param content_disposition (string): Object presentation style
-        @param source_version (string): The source version to copy from
-        @param permissions (dict): Object permissions in the form
-        (all fields are optional)
-        { 'read':[user1, group1, user2, ...],
-        'write':['user3, group2, group3, ...] }
-        @param public (bool): If true, Object is publicly accessible
-        @param metadata (dict): Optional user defined metadata in the form
-        {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
+
+        :param if_etag_match: (string) if provided, return only results
+            with etag matching with this
+
+        :param if_etag_not_match: (string) if provided, return only results
+            with etag not matching with this
+
+        :param destination: (string) The destination path in the form
+            /<container>/<object>
+
+        :param destination_account: (string) The destination account to copy to
+
+        :param content_type: (string) The MIME content type of the object
+
+        :param content_encoding: (string) The encoding of the object
+
+        :param content_disposition: (string) Object presentation style
+
+        :param source_version: (string) The source version to copy from
+
+        :param permissions: (dict) Object permissions in the form
+            (all fields are optional)
+            { 'read':[user1, group1, user2, ...],
+            'write':['user3, group2, group3, ...] }
+
+        :param public: (bool) If true, Object is publicly accessible
+
+        :param metadata: (dict) Optional user defined metadata in the form
+            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
+
+        :returns: ConnectionResponse
         """
 
         self.assert_container()
@@ -671,34 +817,56 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ POST at object level
+
         --- request parameters ---
-        @param format (string): json (default) or xml
-        @param update (bool): Do not replace metadata
+
+        :param format: (string) json (default) or xml
+
+        :param update: (bool) Do not replace metadata
+
         --- request headers ---
-        @param if_etag_match (string): if provided, return only results
-        with etag matching with this
-        @param if_etag_not_match (string): if provided, return only results
-        with etag not matching with this
-        @param content_length (string): The size of the data written
-        @param content_type (string): The MIME content type of the object
-        @param content_range (string): The range of data supplied
-        @param transfer_encoding (string): Set to chunked to specify
-        incremental uploading (if used, Content-Length is ignored)
-        @param content_encoding (string): The encoding of the object
-        @param content_disposition (string): Object presentation style
-        @param source_object (string): Update with data from the object at
-        path /<container>/<object>
-        @param source_account (string): The source account to update from
-        @param source_version (string): The source version to copy from
-        @param object_bytes (integer): The updated objects final size
-        @param manifest (string): Object parts prefix as /<container>/<object>
-        @param permissions (dict): Object permissions in the form (all fields
-        are optional)
-        { 'read':[user1, group1, user2, ...],
-        'write':['user3, group2, group3, ...] }
-        @param public (bool): If true, Object is publicly accessible
-        @param metadata (dict): Optional user defined metadata in the form
-        {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
+
+        :param if_etag_match: (string) if provided, return only results
+            with etag matching with this
+
+        :param if_etag_not_match: (string) if provided, return only results
+            with etag not matching with this
+
+        :param content_length: (string) The size of the data written
+
+        :param content_type: (string) The MIME content type of the object
+
+        :param content_range: (string) The range of data supplied
+
+        :param transfer_encoding: (string) Set to chunked to specify
+            incremental uploading (if used, Content-Length is ignored)
+
+        :param content_encoding: (string) The encoding of the object
+
+        :param content_disposition: (string) Object presentation style
+
+        :param source_object: (string) Update with data from the object at
+            path /<container>/<object>
+
+        :param source_account: (string) The source account to update from
+
+        :param source_version: (string) The source version to copy from
+
+        :param object_bytes: (integer) The updated objects final size
+
+        :param manifest: (string) Object parts prefix as /<container>/<object>
+
+        :param permissions: (dict) Object permissions in the form (all fields
+            are optional)
+            { 'read':[user1, group1, user2, ...],
+            'write':['user3, group2, group3, ...] }
+
+        :param public: (bool) If true, Object is publicly accessible
+
+        :param metadata: (dict) Optional user defined metadata in the form
+            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
+
+        :returns: ConnectionResponse
         """
 
         self.assert_container()
@@ -746,8 +914,12 @@ class PithosRestAPI(StorageClient):
         *args,
         **kwargs):
         """ Full Pithos+ DELETE at object level
+
         --- request parameters ---
-        @param until (string): Optional timestamp
+
+        :param until: (string) Optional timestamp
+
+        :returns: ConnectionResponse
         """
         self.assert_container()