jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/382479 )

Change subject: Docstrings: use Google Style
......................................................................


Docstrings: use Google Style

* Use Google Style Python Docstrings to allow automatically
  generated documentation with Sphinx.

Bug: T159308
Change-Id: Ib46a063964d5701f365c0fe3225854246c643f6b
---
M README.md
M cumin/__init__.py
M cumin/backends/__init__.py
M cumin/backends/direct.py
M cumin/backends/openstack.py
M cumin/backends/puppetdb.py
M cumin/cli.py
M cumin/grammar.py
M cumin/query.py
M cumin/tests/__init__.py
M cumin/tests/integration/test_cli.py
M cumin/tests/unit/test_cli.py
M cumin/transport.py
M cumin/transports/__init__.py
M cumin/transports/clustershell.py
M prospector.yaml
16 files changed, 866 insertions(+), 380 deletions(-)

Approvals:
  jenkins-bot: Verified
  Gehel: Looks good to me, but someone else must approve
  Volans: Looks good to me, approved



diff --git a/README.md b/README.md
index ab7da2f..6af6f73 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@
 ```
 
 Given that the `pyparsing` library defines the grammar in a BNF-like style, 
for the details of the tokens not
-specified above check directly the code in `cumin/grammar.py`.
+specified above check directly the source code in `cumin/grammar.py`.
 
 The `Query` class defined in `cumin/query.py` is the one taking care of 
replacing the aliases, building and executing
 the query parts with their respective backends and aggregating the results. 
Once a query is executed, it returns a
@@ -173,7 +173,7 @@
 ```
 
 Given that the `pyparsing` library used to define the grammar usesa BNF-like 
style, for the details of the tokens not
-specified above check directly the code in `cumin/backends/direct.py`.
+specified above check directly the source code in `cumin/backends/direct.py`.
 
 
 #### Transports
diff --git a/cumin/__init__.py b/cumin/__init__.py
index 2be2cfc..0d556cf 100644
--- a/cumin/__init__.py
+++ b/cumin/__init__.py
@@ -9,6 +9,7 @@
 
 try:
     __version__ = get_distribution(__name__).version
+    """:py:class:`str`: the version of the current Cumin module."""
 except DistributionNotFound:  # pragma: no cover - this should never happen 
during tests
     pass  # package is not installed
 
@@ -34,7 +35,12 @@
 
 
 def trace(self, msg, *args, **kwargs):
-    """Additional logging level for development debugging."""
+    """Additional logging level for development debugging.
+
+    :Parameters:
+        according to :py:class:`logging.Logger` interface for log levels.
+
+    """
     if self.isEnabledFor(LOGGING_TRACE_LEVEL_NUMBER):
         self._log(LOGGING_TRACE_LEVEL_NUMBER, msg, args, **kwargs)  # pragma: 
no cover, pylint: disable=protected-access
 
@@ -58,7 +64,15 @@
         Called by Python's data model for each new instantiation of the class.
 
         Arguments:
-        config -- path to the configuration file to load. [optional, default: 
/etc/cumin/config.yaml]
+            config (str, optional): path to the configuration file to load.
+
+        Returns:
+            dict: the configuration dictionary.
+
+        Examples:
+            >>> import cumin
+            >>> config = cumin.Config()
+
         """
         if config not in cls._instances:
             cls._instances[config] = parse_config(config)
@@ -73,7 +87,14 @@
     """Parse the YAML configuration file.
 
     Arguments:
-    config_file -- the path of the configuration file to load
+        config_file (str): the path of the configuration file to load.
+
+    Returns:
+        dict: the configuration dictionary.
+
+    Raises:
+        CuminError: if unable to read or parse the configuration.
+
     """
     try:
         with open(config_file, 'r') as f:
diff --git a/cumin/backends/__init__.py b/cumin/backends/__init__.py
index a8d0d37..19a6c0e 100644
--- a/cumin/backends/__init__.py
+++ b/cumin/backends/__init__.py
@@ -23,15 +23,16 @@
 
     __metaclass__ = ABCMeta
 
-    """Derived classes must define their own pyparsing grammar and set this 
class attribute accordingly."""
     grammar = pyparsing.NoMatch()  # This grammar will never match.
+    """:py:class:`pyparsing.ParserElement`: derived classes must define their 
own pyparsing grammar and set this class
+    attribute accordingly."""
 
     def __init__(self, config, logger=None):
         """Query constructor.
 
         Arguments:
-        config -- a dictionary with the parsed configuration file
-        logger -- an optional logging.Logger instance [optional, default: None]
+            config (dict): a dictionary with the parsed configuration file.
+            logger (logging.Logger, optional): an optional logger instance.
         """
         self.config = config
         self.logger = logger or logging.getLogger(__name__)
@@ -39,31 +40,40 @@
             name=type(self).__name__, config=config))
 
     def execute(self, query_string):
-        """Build and execute the query, return the list of FQDN hostnames that 
matches.
+        """Build and execute the query, return the NodeSet of FQDN hostnames 
that matches.
 
         Arguments:
-        query_string -- the query string to be parsed and executed
+            query_string (str): the query string to be parsed and executed.
+
+        Returns:
+            ClusterShell.NodeSet.NodeSet: with the FQDNs of the matching hosts.
+
         """
         self._build(query_string)
         return self._execute()
 
     @abstractmethod
     def _execute(self):
-        """Execute the already parsed query and return the list of FQDN 
hostnames that matches."""
+        """Execute the already parsed query and return the NodeSet of FQDN 
hostnames that matches.
+
+        Returns:
+            ClusterShell.NodeSet.NodeSet: with the FQDNs of the matching hosts.
+
+        """
 
     @abstractmethod
     def _parse_token(self, token):
         """Recursively interpret the tokens returned by the grammar parsing.
 
         Arguments:
-        token -- a single token returned by the grammar parsing
+            token (pyparsing.ParseResults): a single token returned by the 
grammar parsing.
         """
 
     def _build(self, query_string):
         """Parse the query string according to the grammar and build the query 
for later execution.
 
         Arguments:
-        query_string -- the query string to be parsed
+            query_string (str): the query string to be parsed.
         """
         self.logger.trace('Parsing query: {query}'.format(query=query_string))
         parsed = self.grammar.parseString(query_string.strip(), parseAll=True)
@@ -75,29 +85,41 @@
 class BaseQueryAggregator(BaseQuery):
     """Query aggregator abstract class.
 
-    Add to BaseQuery the capability of aggregating query subgroups and sub 
tokens into a unified result using common
-    boolean operators for sets: and, or, and not, xor.
+    Add to :py:class:`cumin.backends.BaseQuery` the capability of aggregating 
query subgroups and sub tokens into a
+    unified result using common boolean operators for sets: ``and``, ``or``, 
``and not`` and ``xor``.
     The class has a stack-like structure that must be populated by the derived 
classes while building the query.
     On execution the stack is traversed and the results are aggreagated 
together based on subgroups and boolean
     operators.
     """
 
     def __init__(self, config, logger=None):
-        """Query aggregator constructor, initialize the stack."""
+        """Query aggregator constructor, initialize the stack.
+
+        :Parameters:
+            according to parent :py:meth:`cumin.backends.BaseQuery.__init__`.
+        """
         super(BaseQueryAggregator, self).__init__(config, logger=logger)
 
         self.stack = None
         self.stack_pointer = None
 
     def _build(self, query_string):
-        """Override parent class _build method to reset the stack and log 
it."""
+        """Override parent method to reset the stack and log it.
+
+        :Parameters:
+            according to parent :py:meth:`cumin.backends.BaseQuery._build`.
+        """
         self.stack = self._get_stack_element()
         self.stack_pointer = self.stack
         super(BaseQueryAggregator, self)._build(query_string)
         self.logger.trace('Query stack: {stack}'.format(stack=self.stack))
 
     def _execute(self):
-        """Required by BaseQuery."""
+        """Concrete implementation of parent abstract method.
+
+        :Parameters:
+            according to parent :py:meth:`cumin.backends.BaseQuery._execute`.
+        """
         hosts = NodeSet()
         self._loop_stack(hosts, self.stack)  # The hosts nodeset is updated in 
place while looping the stack
         self.logger.debug('Found {num} hosts'.format(num=len(hosts)))
@@ -117,20 +139,29 @@
 
     @abstractmethod
     def _parse_token(self, token):
-        """Required by BaseQuery."""
+        """Re-define abstract method from parent abstract class.
+
+        :Parameters:
+            according to parent 
:py:meth:`cumin.backends.BaseQuery._parse_token`.
+        """
 
     @staticmethod
     def _get_stack_element():
-        """Return an empty stack element."""
+        """Return an empty stack element.
+
+        Returns:
+            dict: the dictionary with an empty stack element.
+
+        """
         return {'hosts': None, 'children': [], 'parent': None, 'bool': None}
 
     def _loop_stack(self, hosts, stack_element):
         """Loop the stack generated while parsing the query and aggregate the 
results.
 
         Arguments:
-        hosts         -- the NodeSet of hosts to update with the current stack 
element results. This object is updated
-                         in place by reference.
-        stack_element -- the stack element to iterate
+            hosts (ClusterShell.NodeSet.NodeSet): the hosts to be updated with 
the current stack element results. This
+                object is updated in place by reference.
+            stack_element (dict): the stack element to iterate.
         """
         if stack_element['hosts'] is None:
             element_hosts = NodeSet()
@@ -142,14 +173,15 @@
         self._aggregate_hosts(hosts, element_hosts, stack_element['bool'])
 
     def _aggregate_hosts(self, hosts, element_hosts, bool_operator):
-        """.
+        """Aggregate hosts according to their boolean operator.
 
         Arguments:
-        hosts         -- the NodeSet of hosts to update with the results in 
element_hosts according to the
-                         bool_operator. This object is updated in place by 
reference.
-        element_hosts -- the NodeSet of additional hosts to aggregate to the 
results based on the bool_operator
-        bool_operator -- the boolean operator to apply while aggregating the 
two NodeSet. It must be None when adding
-                         the first hosts.
+            hosts (ClusterShell.NodeSet.NodeSet): the hosts to update with the 
results in ``element_hosts`` according
+                to the bool_operator. This object is updated in place by 
reference.
+            element_hosts (ClusterShell.NodeSet.NodeSet): the additional hosts 
to aggregate to the results based on
+                the ``bool_operator``.
+            bool_operator (str, None): the boolean operator to apply while 
aggregating the two NodeSet. It must be
+                :py:data:`None` when adding the first hosts.
         """
         self.logger.trace("Aggregating: {hosts} | {boolean} | 
{element_hosts}".format(
             hosts=hosts, boolean=bool_operator, element_hosts=element_hosts))
diff --git a/cumin/backends/direct.py b/cumin/backends/direct.py
index 24bb670..0a9f6e8 100644
--- a/cumin/backends/direct.py
+++ b/cumin/backends/direct.py
@@ -10,18 +10,24 @@
     """Define the query grammar.
 
     Some query examples:
-    - Simple selection: host1.domain
-    - ClusterShell syntax for hosts expansion: 
host10[10-42].domain,host2010.other-domain
-    - A complex selection:
-      host100[1-5].domain or (host10[30-40].domain and (host10[10-42].domain 
and not host33.domain))
 
-    Backus-Naur form (BNF) of the grammar:
-            <grammar> ::= <item> | <item> <boolean> <grammar>
-               <item> ::= <hosts> | "(" <grammar> ")"
-            <boolean> ::= "and not" | "and" | "xor" | "or"
+    * Simple selection: ``host1.domain``
+    * ClusterShell syntax for hosts expansion: 
``host10[10-42].domain,host2010.other-domain``
+    * A complex selection:
+      ``host100[1-5].domain or (host10[30-40].domain and (host10[10-42].domain 
and not host33.domain))``
+
+    Backus-Naur form (BNF) of the grammar::
+
+        <grammar> ::= <item> | <item> <boolean> <grammar>
+           <item> ::= <hosts> | "(" <grammar> ")"
+        <boolean> ::= "and not" | "and" | "xor" | "or"
 
     Given that the pyparsing library defines the grammar in a BNF-like style, 
for the details of the tokens not
-    specified above check directly the code.
+    specified above check directly the source code.
+
+    Returns:
+        pyparsing.ParserElement: the grammar parser.
+
     """
     # Boolean operators
     boolean = (pp.CaselessKeyword('and not').leaveWhitespace() | 
pp.CaselessKeyword('and') |
@@ -46,19 +52,23 @@
 class DirectQuery(BaseQueryAggregator):
     """DirectQuery query builder.
 
-    The 'direct' backend allow to use Cumin without any external dependency 
for the hosts selection.
+    The `direct` backend allow to use Cumin without any external dependency 
for the hosts selection.
     It allow to write arbitrarily complex queries with subgroups and boolean 
operators, but each item must be either the
-    hostname itself, or the using host expansion using the powerful 
ClusterShell NodeSet syntax, see:
-        https://clustershell.readthedocs.io/en/latest/api/NodeSet.html
+    hostname itself, or the using host expansion using the powerful 
:py:class:`ClusterShell.NodeSet.NodeSet` syntax.
 
-    The typical usage for the 'direct' backend is as a reliable alternative in 
cases in which the primary host
+    The typical usage for the `direct` backend is as a reliable alternative in 
cases in which the primary host
     selection mechanism is not working and also for testing the transports 
without any external backend dependency.
     """
 
     grammar = grammar()
+    """:py:class:`pyparsing.ParserElement`: load the grammar parser only once 
in a singleton-like way."""
 
     def _parse_token(self, token):
-        """Required by BaseQuery."""
+        """Concrete implementation of parent abstract method.
+
+        :Parameters:
+            according to parent 
:py:meth:`cumin.backends.BaseQueryAggregator._parse_token`.
+        """
         if not isinstance(token, pp.ParseResults):  # pragma: no cover - this 
should never happen
             raise InvalidQueryError('Expecting ParseResults object, got 
{type}: {token}'.format(
                 type=type(token), token=token))
@@ -85,6 +95,9 @@
             raise InvalidQueryError('Got unexpected token: 
{token}'.format(token=token))
 
 
-# Required by the backend auto-loader in 
cumin.grammar.get_registered_backends()
 GRAMMAR_PREFIX = 'D'
+""":py:class:`str`: the prefix associate to this grammar, to register this 
backend into the general grammar.
+Required by the backend auto-loader in 
:py:meth:`cumin.grammar.get_registered_backends`."""
+
 query_class = DirectQuery  # pylint: disable=invalid-name
+"""Required by the backend auto-loader in 
:py:meth:`cumin.grammar.get_registered_backends`."""
diff --git a/cumin/backends/openstack.py b/cumin/backends/openstack.py
index 0c28f96..b2e1b71 100644
--- a/cumin/backends/openstack.py
+++ b/cumin/backends/openstack.py
@@ -14,22 +14,28 @@
     """Define the query grammar.
 
     Some query examples:
-    - All hosts in all OpenStack projects: `*`
-    - All hosts in a specific OpenStack project: `project:project_name`
-    - Filter hosts using any parameter allowed by the OpenStack list-servers 
API: `name:host1 image:UUID`
-      See https://developer.openstack.org/api-ref/compute/#list-servers for 
more details.
-      Multiple filters can be added separated by space. The value can be 
enclosed in single or double quotes.
-      If the `project` key is not specified the hosts will be selected from 
all projects.
-    - To mix multiple selections the general grammar must be used with 
multiple subqueries:
-      `O{project:project1} or O{project:project2}`
 
-    Backus-Naur form (BNF) of the grammar:
-            <grammar> ::= "*" | <items>
-              <items> ::= <item> | <item> <whitespace> <items>
-               <item> ::= <key>:<value>
+    * All hosts in all OpenStack projects: ``*``
+    * All hosts in a specific OpenStack project: ``project:project_name``
+    * Filter hosts using any parameter allowed by the OpenStack list-servers 
API: ``name:host1 image:UUID``
+      See `OpenStack Compute API list-servers 
<https://developer.openstack.org/api-ref/compute/#list-servers>`_ for
+      more details. Multiple filters can be added separated by space. The 
value can be enclosed in single or double
+      quotes. If the ``project`` key is not specified the hosts will be 
selected from all projects.
+    * To mix multiple selections the general grammar must be used with 
multiple subqueries:
+      ``O{project:project1} or O{project:project2}``
+
+    Backus-Naur form (BNF) of the grammar::
+
+        <grammar> ::= "*" | <items>
+          <items> ::= <item> | <item> <whitespace> <items>
+           <item> ::= <key>:<value>
 
     Given that the pyparsing library defines the grammar in a BNF-like style, 
for the details of the tokens not
-    specified above check directly the code.
+    specified above check directly the source code.
+
+    Returns:
+        pyparsing.ParserElement: the grammar parser.
+
     """
     quoted_string = pp.quotedString.copy().addParseAction(pp.removeQuotes)  # 
Both single and double quotes are allowed
 
@@ -49,8 +55,12 @@
     """Return a new keystone session based on configuration.
 
     Arguments:
-    config  -- a dictionary with the session configuration: auth_url, 
username, password
-    project -- a project to scope the session to. [optional, default: None]
+        config (dict): a dictionary with the session configuration keys: 
``auth_url``, ``username``, ``password``.
+        project (str, optional): a project to scope the session to.
+
+    Returns:
+        keystoneauth1.session.Session: the Keystone session scoped for the 
project if specified.
+
     """
     auth = keystone_identity.Password(
         auth_url='{auth_url}/v3'.format(auth_url=config.get('auth_url', 
'http://localhost:5000')),
@@ -66,8 +76,13 @@
     """Return a new nova client tailored to the given project.
 
     Arguments:
-    config  -- a dictionary with the session configuration: auth_url, 
username, password, nova_api_version, timeout
-    project -- a project to scope the session to. [optional, default: None]
+        config (dict): a dictionary with the session configuration keys: 
``auth_url``, ``username``, ``password``,
+            ``nova_api_version``, ``timeout``.
+        project (str): the project to scope the `novaclient` session to.
+
+    Returns:
+        novaclient.client.Client: the novaclient Client instance, already 
authenticated.
+
     """
     return nova_client.Client(
         config.get('nova_api_version', '2'),
@@ -83,11 +98,14 @@
     """
 
     grammar = grammar()
+    """:py:class:`pyparsing.ParserElement`: load the grammar parser only once 
in a singleton-like way."""
 
     def __init__(self, config, logger=None):
-        """Query constructor for the OpenStack backend.
+        """Override parent class constructor for specific setup.
 
-        Arguments: according to BaseQuery interface
+        :Parameters:
+            according to parent :py:meth:`cumin.backends.BaseQuery.__init__`.
+
         """
         super(OpenStackQuery, self).__init__(config, logger=logger)
         self.openstack_config = self.config.get('openstack', {})
@@ -95,7 +113,12 @@
         self.search_params = self._get_default_search_params()
 
     def _get_default_search_params(self):
-        """Return the default search parameters dictionary and set the 
project, if configured."""
+        """Return the default search parameters dictionary and set the 
project, if configured.
+
+        Returns:
+            dict: the dictionary with the default search parameters.
+
+        """
         params = {'status': 'ACTIVE', 'vm_state': 'ACTIVE'}
         config_params = self.openstack_config.get('query_params', {})
 
@@ -106,12 +129,25 @@
         return params
 
     def _build(self, query_string):
-        """Override parent class _build method to reset search parameters."""
+        """Override parent class _build method to reset the search parameters.
+
+        :Parameters:
+            according to parent :py:meth:`cumin.backends.BaseQuery._build`.
+
+        """
         self.search_params = self._get_default_search_params()
         super(OpenStackQuery, self)._build(query_string)
 
     def _execute(self):
-        """Required by BaseQuery."""
+        """Concrete implementation of parent abstract method.
+
+        :Parameters:
+            according to parent :py:meth:`cumin.backends.BaseQuery._execute`.
+
+        Returns:
+            ClusterShell.NodeSet.NodeSet: with the FQDNs of the matching hosts.
+
+        """
         if self.search_project is None:
             hosts = NodeSet()
             for project in self._get_projects():
@@ -122,7 +158,15 @@
         return hosts
 
     def _parse_token(self, token):
-        """Required by BaseQuery."""
+        """Concrete implementation of parent abstract method.
+
+        :Parameters:
+            according to parent 
:py:meth:`cumin.backends.BaseQuery._parse_token`.
+
+        Raises:
+            cumin.backends.InvalidQueryError: on internal parsing error.
+
+        """
         if not isinstance(token, pp.ParseResults):  # pragma: no cover - this 
should never happen
             raise InvalidQueryError('Expecting ParseResults object, got 
{type}: {token}'.format(
                 type=type(token), token=token))
@@ -141,7 +185,12 @@
             raise InvalidQueryError('Got unexpected token: 
{token}'.format(token=token))
 
     def _get_projects(self):
-        """Yield the project names for all projects (except admin) from 
keystone API."""
+        """Get all the project names from keystone API, filtering out the 
special `admin` project. Is a `generator`.
+
+        Yields:
+            str: the project name for all the selected projects.
+
+        """
         client = keystone_client.Client(
             session=_get_keystone_session(self.openstack_config), 
timeout=self.openstack_config.get('timeout', 10))
         return (project.name for project in client.projects.list(enabled=True) 
if project.name != 'admin')
@@ -150,7 +199,11 @@
         """Return a NodeSet with the list of matching hosts based for the 
project based on the search parameters.
 
         Arguments:
-        project -- the project name where to get the list of hosts
+            project (str): the project name where to get the list of hosts.
+
+        Returns:
+            ClusterShell.NodeSet.NodeSet: with the FQDNs of the matching hosts.
+
         """
         client = _get_nova_client(self.openstack_config, project)
 
@@ -166,6 +219,9 @@
                                 for server in 
client.servers.list(search_opts=self.search_params))
 
 
-# Required by the backend auto-loader in 
cumin.grammar.get_registered_backends()
 GRAMMAR_PREFIX = 'O'
+""":py:class:`str`: the prefix associate to this grammar, to register this 
backend into the general grammar.
+Required by the backend auto-loader in 
:py:meth:`cumin.grammar.get_registered_backends`."""
+
 query_class = OpenStackQuery  # pylint: disable=invalid-name
+"""Required by the backend auto-loader in 
:py:meth:`cumin.grammar.get_registered_backends`."""
diff --git a/cumin/backends/puppetdb.py b/cumin/backends/puppetdb.py
index 877a1e7..190a3d8 100644
--- a/cumin/backends/puppetdb.py
+++ b/cumin/backends/puppetdb.py
@@ -12,46 +12,54 @@
 from cumin.backends import BaseQuery, InvalidQueryError
 
 
-# Available categories
 CATEGORIES = (
     'F',  # Fact
     'R',  # Resource
 )
+""":py:func:`tuple`: available categories in the grammar."""
 
-# Available operators
 OPERATORS = ('=', '>=', '<=', '<', '>', '~')
+""":py:func:`tuple`: available operators in the grammar, the same available in 
PuppetDB API."""
 
 
 def grammar():
     """Define the query grammar.
 
     Some query examples:
-    - All hosts: `*`
-    - Hosts globbing: `host10*`
-    - ClusterShell's NodeSet syntax (see 
https://clustershell.readthedocs.io/en/latest/api/NodeSet.html) for hosts
-      expansion: `host10[10-42].domain`
-    - Category based key-value selection:
-      - `R:Resource::Name`: query all the hosts that have a resource of type 
`Resource::Name`.
-      - `R:Resource::Name = 'resource-title'`: query all the hosts that have a 
resource of type `Resource::Name` whose
-        title is `resource-title`. For example `R:Class = MyModule::MyClass`.
-      - `R:Resource::Name@field = 'some-value'`: query all the hosts that have 
a resource of type `Resource::Name`
-        whose field `field` has the value `some-value`. The valid fields are: 
`tag`, `certname`, `type`, `title`,
-        `exported`, `file`, `line`. The previous syntax is a shortcut for this 
one with the field `title`.
-      - `R:Resource::Name%param = 'some-value'`: query all the hosts that have 
a resource of type `Resource::Name`
-        whose parameter `param` has the value `some-value`.
-      - Mixed facts/resources queries are not supported, but the same result 
can be achieved by the main grammar using
-        multiple subqueries.
-    - A complex selection for facts:
-      `host10[10-42].*.domain or (not F:key1 = value1 and host10*) or (F:key2 
> value2 and F:key3 ~ '^value[0-9]+')`
 
-    Backus-Naur form (BNF) of the grammar:
+    * All hosts: ``*``
+    * Hosts globbing: ``host10*``
+    * :py:class:`ClusterShell.NodeSet.NodeSet` syntax for hosts expansion: 
``host10[10-42].domain``
+    * Category based key-value selection:
+
+      * ``R:Resource::Name``: query all the hosts that have a resource of type 
`Resource::Name`.
+      * ``R:Resource::Name = 'resource-title'``: query all the hosts that have 
a resource of type `Resource::Name`
+        whose title is ``resource-title``. For example ``R:Class = 
MyModule::MyClass``.
+      * ``R:Resource::Name@field = 'some-value'``: query all the hosts that 
have a resource of type ``Resource::Name``
+        whose field ``field`` has the value ``some-value``. The valid fields 
are: ``tag``, ``certname``, ``type``,
+        ``title``, ``exported``, ``file``, ``line``. The previous syntax is a 
shortcut for this one with the field
+        ``title``.
+      * ``R:Resource::Name%param = 'some-value'``: query all the hosts that 
have a resource of type ``Resource::Name``
+        whose parameter ``param`` has the value ``some-value``.
+      * Mixed facts/resources queries are not supported, but the same result 
can be achieved by the main grammar using
+        multiple subqueries.
+
+    * A complex selection for facts:
+      ``host10[10-42].*.domain or (not F:key1 = value1 and host10*) or (F:key2 
> value2 and F:key3 ~ '^value[0-9]+')``
+
+    Backus-Naur form (BNF) of the grammar::
+
             <grammar> ::= <item> | <item> <and_or> <grammar>
                <item> ::= [<neg>] <query-token> | [<neg>] "(" <grammar> ")"
         <query-token> ::= <token> | <hosts>
               <token> ::= <category>:<key> [<operator> <value>]
 
     Given that the pyparsing library defines the grammar in a BNF-like style, 
for the details of the tokens not
-    specified above check directly the code.
+    specified above check directly the source code.
+
+    Returns:
+        pyparsing.ParserElement: the grammar parser.
+
     """
     # Boolean operators
     and_or = (pp.CaselessKeyword('and') | pp.CaselessKeyword('or'))('bool')
@@ -92,19 +100,31 @@
 class PuppetDBQuery(BaseQuery):
     """PuppetDB query builder.
 
-    The 'puppetdb' backend allow to use an existing PuppetDB instance for the 
hosts selection.
+    The `puppetdb` backend allow to use an existing PuppetDB instance for the 
hosts selection.
     At the moment only PuppetDB v3 API are implemented.
     """
 
     base_url_template = 'https://{host}:{port}/v3/'
+    """:py:class:`str`: string template in the :py:meth:`str.format` style 
used to generate the base URL of the
+    PuppetDB server."""
+
     endpoints = {'R': 'resources', 'F': 'nodes'}
+    """:py:class:`dict`: dictionary with the mapping of the available 
categories in the grammar to the PuppetDB API
+    endpoints."""
+
     hosts_keys = {'R': 'certname', 'F': 'name'}
+    """:py:class:`dict`: dictionary with the mapping of the available 
categories in the grammar to the PuppetDB API
+    field to query to get the hostname."""
+
     grammar = grammar()
+    """:py:class:`pyparsing.ParserElement`: load the grammar parser only once 
in a singleton-like way."""
 
     def __init__(self, config, logger=None):
         """Query constructor for the PuppetDB backend.
 
-        Arguments: according to BaseQuery interface
+        :Parameters:
+            according to parent :py:meth:`cumin.backends.BaseQuery.__init__`.
+
         """
         super(PuppetDBQuery, self).__init__(config, logger=logger)
         self.grouped_tokens = None
@@ -120,16 +140,24 @@
 
     @property
     def category(self):
-        """Getter for the property category with a default value."""
+        """Category for the current query.
+
+        :Getter:
+            Returns the current `category` or a default value if not set.
+
+        :Setter:
+            :py:class:`str`: the value to set the `category` to.
+
+        Raises:
+            cumin.backends.InvalidQueryError: if trying to set it to an 
invalid `category` or mixing categories in a
+                single query.
+
+        """
         return self._category or 'F'
 
     @category.setter
     def category(self, value):
-        """Setter for the property category with validation.
-
-        Arguments:
-        value -- the value to set the category to
-        """
+        """Setter for the `category` property. The relative documentation is 
in the getter."""
         if value not in self.endpoints:
             raise InvalidQueryError("Invalid value '{category}' for category 
property".format(category=value))
         if self._category is not None and value != self._category:
@@ -150,18 +178,36 @@
 
     @staticmethod
     def _get_grouped_tokens():
-        """Return an empty grouped tokens structure."""
+        """Return an empty grouped tokens structure.
+
+        Returns:
+            dict: the dictionary with the empty grouped tokens structure.
+
+        """
         return {'parent': None, 'bool': None, 'tokens': []}
 
     def _build(self, query_string):
-        """Override parent class _build method to reset tokens and add 
logging."""
+        """Override parent class _build method to reset tokens and add logging.
+
+        :Parameters:
+            according to parent :py:meth:`cumin.backends.BaseQuery._build`.
+
+        """
         self.grouped_tokens = PuppetDBQuery._get_grouped_tokens()
         self.current_group = self.grouped_tokens
         super(PuppetDBQuery, self)._build(query_string)
         self.logger.trace('Query tokens: 
{tokens}'.format(tokens=self.grouped_tokens))
 
     def _execute(self):
-        """Required by BaseQuery."""
+        """Concrete implementation of parent abstract method.
+
+        :Parameters:
+            according to parent :py:meth:`cumin.backends.BaseQuery._execute`.
+
+        Returns:
+            ClusterShell.NodeSet.NodeSet: with the FQDNs of the matching hosts.
+
+        """
         query = 
self._get_query_string(group=self.grouped_tokens).format(host_key=self.hosts_keys[self.category])
         hosts = self._api_call(query, self.endpoints[self.category])
         unique_hosts = NodeSet.fromlist([host[self.hosts_keys[self.category]] 
for host in hosts])
@@ -174,11 +220,15 @@
         """Add a category token to the query 'F:key = value'.
 
         Arguments:
-        category -- the category of the token, one of CATEGORIES excluding the 
alias one.
-        key      -- the key for this category
-        value    -- the value to match, if not specified the key itself will 
be matched [optional, default: None]
-        operator -- the comparison operator to use, one of 
cumin.grammar.OPERATORS [optional: default: =]
-        neg      -- whether the token must be negated [optional, default: 
False]
+            category (str): the category of the token, one of 
:py:const:`CATEGORIES`.
+            key (str): the key for this category.
+            value (str, optional): the value to match, if not specified the 
key itself will be matched.
+            operator (str, optional): the comparison operator to use, one of 
:py:const:`OPERATORS`.
+            neg (bool, optional): whether the token must be negated.
+
+        Raises:
+            cumin.backends.InvalidQueryError: on internal parsing error.
+
         """
         self.category = category
         if operator == '~':
@@ -201,8 +251,8 @@
         """Add a list of hosts to the query.
 
         Arguments:
-        hosts -- a list of hosts to match
-        neg   -- whether the token must be negated [optional, default: False]
+            hosts (ClusterShell.NodeSet.NodeSet): with the list of hosts to 
search.
+            neg (bool, optional): whether the token must be negated.
         """
         if not hosts:
             return
@@ -224,7 +274,15 @@
         self.current_group['tokens'].append(query)
 
     def _parse_token(self, token):
-        """Required by BaseQuery."""
+        """Concrete implementation of parent abstract method.
+
+        :Parameters:
+            according to parent 
:py:meth:`cumin.backends.BaseQuery._parse_token`.
+
+        Raises:
+            cumin.backends.InvalidQueryError: on internal parsing error.
+
+        """
         if isinstance(token, str):
             return
 
@@ -252,12 +310,19 @@
                 "No valid key found in token, one of bool|hosts|category 
expected: {token}".format(token=token_dict))
 
     def _get_resource_query(self, key, value=None, operator='='):  # pylint: 
disable=no-self-use
-        """Build a resource query based on the parameters, resolving the 
special cases for %params and @field.
+        """Build a resource query based on the parameters, resolving the 
special cases for ``%params`` and ``@field``.
 
         Arguments:
-        key      -- the key of the resource
-        value    -- the value to match, if not specified the key itself will 
be matched [optional, default: None]
-        operator -- the comparison operator to use, one of 
cumin.grammar.OPERATORS [optional: default: =]
+            key (str): the key of the resource.
+            value (str, optional): the value to match, if not specified the 
key itself will be matched.
+            operator (str, optional): the comparison operator to use, one of 
:py:const:`OPERATORS`.
+
+        Returns:
+            str: the resource query.
+
+        Raises:
+            cumin.backends.InvalidQueryError: on invalid combinations of 
parameters.
+
         """
         if all(char in key for char in ('%', '@')):
             raise InvalidQueryError(("Resource key cannot contain both '%' 
(query a resource's parameter) and '@' "
@@ -293,7 +358,11 @@
         """Recursively build and return the PuppetDB query string.
 
         Arguments:
-        group -- a dictionary with the grouped tokens
+            group (dict): a dictionary with the grouped tokens.
+
+        Returns:
+            str: the query string for the PuppetDB API.
+
         """
         if group['bool']:
             query = '["{bool}", '.format(bool=group['bool'])
@@ -319,7 +388,11 @@
         """Add a boolean AND or OR query block to the query and validate logic.
 
         Arguments:
-        bool_op -- the boolean operator (and|or) to add to the query
+            bool_op (str): the boolean operator to add to the query: ``and``, 
``or``.
+
+        Raises:
+            cumin.backends.InvalidQueryError: if an invalid boolean operator 
was found.
+
         """
         if self.current_group['bool'] is None:
             self.current_group['bool'] = bool_op
@@ -333,14 +406,21 @@
         """Execute a query to PuppetDB API and return the parsed JSON.
 
         Arguments:
-        query    -- the query parameter to send to the PuppetDB API
-        endpoint -- the endpoint of the PuppetDB API to call
+            query (str): the query parameter to send to the PuppetDB API.
+            endpoint (str): the endpoint of the PuppetDB API to call.
+
+        Raises:
+            requests.HTTPError: if the PuppetDB API call fails.
+
         """
         resources = requests.get(self.url + endpoint, params={'query': query}, 
verify=True)
         resources.raise_for_status()
         return resources.json()
 
 
-# Required by the backend auto-loader in 
cumin.grammar.get_registered_backends()
 GRAMMAR_PREFIX = 'P'
+""":py:class:`str`: the prefix associate to this grammar, to register this 
backend into the general grammar.
+Required by the backend auto-loader in 
:py:meth:`cumin.grammar.get_registered_backends`."""
+
 query_class = PuppetDBQuery  # pylint: disable=invalid-name
+"""Required by the backend auto-loader in 
:py:meth:`cumin.grammar.get_registered_backends`."""
diff --git a/cumin/cli.py b/cumin/cli.py
index f9e16ce..718e116 100644
--- a/cumin/cli.py
+++ b/cumin/cli.py
@@ -22,23 +22,25 @@
 
 
 logger = logging.getLogger(__name__)  # pylint: disable=invalid-name
+"""logging.Logger: The logging instance."""
 OUTPUT_FORMATS = ('txt', 'json')
+"""tuple: A tuple with the possible output formats."""
 INTERACTIVE_BANNER = """===== Cumin Interactive REPL =====
 # Press Ctrl+d or type exit() to exit the program.
 
 = Available variables =
-# hosts     -- the ClusterShell NodeSet of targeted hosts.
-# worker    -- the instance of the Transport worker that was used for the 
execution.
-# args      -- the parsed command line arguments, an argparse.Namespace 
instance.
-# config    -- the cofiguration dictionary.
-# exit_code -- the return code of the execution, that will be used as exit 
code.
+#     hosts: the ClusterShell NodeSet of targeted hosts.
+#     worker: the instance of the Transport worker that was used for the 
execution.
+#     args: the parsed command line arguments, an argparse.Namespace instance.
+#     config: the cofiguration dictionary.
+#     exit_code: the return code of the execution, that will be used as exit 
code.
 
 = Useful functions =
-# worker.get_results() -- generator that yields the tuple (nodes, output) for 
each grouped result, where:
-#                         - nodes  -- is a ClusterShell.NodeSet.NodeSet 
instance
-#                         - output -- is a ClusterShell.MsgTree.MsgTreeElem 
instance
-# h()                  -- print this help message.
-# help(object)         -- Python default interactive help and documentation of 
the given object.
+#     worker.get_results(): generator that yields the tuple (nodes, output) 
for each grouped result, where:
+#                         -     nodes: is a ClusterShell.NodeSet.NodeSet 
instance
+#                         -     output: is a ClusterShell.MsgTree.MsgTreeElem 
instance
+#     h(): print this help message.
+#     help(object): Python default interactive help and documentation of the 
given object.
 
 = Example usage:
 for nodes, output in worker.get_results():
@@ -46,6 +48,7 @@
     print(output)
     print('-----')
 """
+"""str: The message to print when entering the intractive REPL mode."""
 
 
 class KeyboardInterruptError(cumin.CuminError):
@@ -56,7 +59,7 @@
     """Parse command line arguments and return them.
 
     Arguments:
-    argv -- the list of arguments to use. If None, the command line ones are 
used [optional, default: None]
+        argv: the list of arguments to use. If None, the command line ones are 
used [optional, default: None]
     """
     sync_mode = 'sync'
     async_mode = 'async'
@@ -116,7 +119,7 @@
                         help='Set log level to TRACE, a custom logging level 
intended for development debugging.')
     parser.add_argument('hosts', metavar='HOSTS_QUERY', help='Hosts selection 
query')
     parser.add_argument('commands', metavar='COMMAND', nargs='*',
-                        help='Command to be executed. If no commands are 
speficied, --dry-run is set.')
+                        help='Command to be executed. If no commands are 
specified, --dry-run is set.')
 
     if argv is None:
         parsed_args = parser.parse_args()
@@ -157,8 +160,8 @@
     """Setup the logger instance.
 
     Arguments:
-    filename -- the filename of the log file
-    debug    -- whether to set logging level to DEBUG [optional, default: 
False]
+        filename: the filename of the log file
+        debug: whether to set logging level to DEBUG [optional, default: False]
     """
     file_path = os.path.dirname(filename)
     if not os.path.exists(file_path):
@@ -183,8 +186,8 @@
     """Signal handler for Ctrl+c / SIGINT, raises KeyboardInterruptError.
 
     Arguments (as defined in https://docs.python.org/2/library/signal.html):
-    signum -- the signal number
-    frame  -- the current stack frame
+        signum: the signal number
+        frame: the current stack frame
     """
     if not sys.stdout.isatty():  # pylint: disable=no-member
         logger.warning('Execution interrupted by Ctrl+c/SIGINT')
@@ -224,8 +227,8 @@
     r"""Print a message to stderr and flush.
 
     Arguments:
-    message -- the message to print to sys.stderr
-    end     -- the character to use at the end of the message. [optional, 
default: \n]
+        message: the message to print to sys.stderr
+        end: the character to use at the end of the message. [optional, 
default: \n]
     """
     tqdm.write('{color}{message}{reset}'.format(
         color=colorama.Fore.YELLOW, message=message, 
reset=colorama.Style.RESET_ALL), file=sys.stderr, end=end)
@@ -235,8 +238,8 @@
     """Resolve the hosts selection into a list of hosts and return it. Raises 
KeyboardInterruptError.
 
     Arguments:
-    args   -- ArgumentParser instance with parsed command line arguments
-    config -- a dictionary with the parsed configuration file
+        args: ArgumentParser instance with parsed command line arguments
+        config: a dictionary with the parsed configuration file
     """
     hosts = query.Query(config, logger=logger).execute(args.hosts)
 
@@ -280,8 +283,8 @@
     """Print the execution results in a specific format.
 
     Arguments:
-    output_format -- the output format to use, one of: 'txt', 'json'.
-    worker        -- the Transport worker instance to retrieve the results 
from.
+        output_format: the output format to use, one of: 'txt', 'json'.
+        worker: the Transport worker instance to retrieve the results from.
     """
     if output_format not in OUTPUT_FORMATS:
         raise cumin.CuminError("Got invalid output format '{fmt}', expected 
one of {allowed}".format(
@@ -306,8 +309,8 @@
     """Execute the commands on the selected hosts and print the results.
 
     Arguments:
-    args   -- ArgumentParser instance with parsed command line arguments
-    config -- a dictionary with the parsed configuration file
+        args: ArgumentParser instance with parsed command line arguments
+        config: a dictionary with the parsed configuration file
     """
     hosts = get_hosts(args, config)
     if not hosts:
@@ -345,7 +348,7 @@
     """CLI entry point. Execute commands on hosts according to arguments.
 
     Arguments:
-    argv -- the list of arguments to use. If None, the command line ones are 
used [optional, default: None]
+        argv: the list of arguments to use. If None, the command line ones are 
used [optional, default: None]
     """
     signal.signal(signal.SIGINT, sigint_handler)
     colorama.init()
diff --git a/cumin/grammar.py b/cumin/grammar.py
index 40104cb..2a93c0e 100644
--- a/cumin/grammar.py
+++ b/cumin/grammar.py
@@ -6,15 +6,29 @@
 
 import pyparsing as pp
 
-
 from cumin import backends, CuminError
 
-# Backend object
+
 Backend = namedtuple('Backend', ['keyword', 'name', 'cls'])
+""":py:func:`collections.namedtuple` that define a Backend object.
+
+Keyword Arguments:
+    keyword (str): The backend keyword to be used in the grammar.
+    name (str): The backend name.
+    cls (BaseQuery): The backend class object.
+"""
 
 
 def get_registered_backends():
-    """Return a list of Backend objects for all the registered backends."""
+    """Get a mapping of all the registered backends with their keyword.
+
+    Returns:
+        dict: A dictionary with a ``{keyword: Backend object}`` mapping for 
each available backend.
+
+    Raises:
+        cumin.CuminError: If unable to register a backend because the key is 
already used by another backend.
+
+    """
     available_backends = {}
     backend_names = [name for _, name, ispkg in 
pkgutil.iter_modules(backends.__path__) if not ispkg]
 
@@ -30,32 +44,38 @@
     return available_backends
 
 
-# Register all the backends only once
 REGISTERED_BACKENDS = get_registered_backends()
+""":py:class:`dict`: Hold the dictionary of available backends generated at 
load time mapped by their keyword."""
 
 
 def grammar():
     """Define the main multi-query grammar.
 
     Cumin provides a user-friendly generic query language that allows to 
combine the results of subqueries for multiple
-    backends.
+    backends:
 
-    - Each query part can be composed with the others using boolean operators 
(`and`, `or`, `and not`, `xor`).
-    - Multiple query parts can be grouped together with parentheses (`(`, `)`).
-    - Specific backend query (`I{backend-specific query syntax}`, where `I` is 
an identifier for the specific backend).
-    - Alias replacement, according to aliases defined in the configuration 
file (`A:group1`).
-    - The identifier `A` is reserved for the aliases replacement and cannot be 
used to identify a backend.
-    - A complex query example: `(D{host1 or host2} and (P{R:Class = 
Role::MyClass} and not A:group1)) or D{host3}`
+    * Each query part can be composed with the others using boolean operators 
``and``, ``or``, ``and not``, ``xor``.
+    * Multiple query parts can be grouped together with parentheses ``(``, 
``)``.
+    * Specific backend query ``I{backend-specific query syntax}``, where ``I`` 
is an identifier for the specific
+      backend.
+    * Alias replacement, according to aliases defined in the configuration 
file ``A:group1``.
+    * The identifier ``A`` is reserved for the aliases replacement and cannot 
be used to identify a backend.
+    * A complex query example: ``(D{host1 or host2} and (P{R:Class = 
Role::MyClass} and not A:group1)) or D{host3}``
 
-    Backus-Naur form (BNF) of the grammar:
-            <grammar> ::= <item> | <item> <boolean> <grammar>
-               <item> ::= <backend_query> | <alias> | "(" <grammar> ")"
-      <backend_query> ::= <backend> "{" <query> "}"
-              <alias> ::= A:<alias_name>
-            <boolean> ::= "and not" | "and" | "xor" | "or"
+    Backus-Naur form (BNF) of the grammar::
+
+              <grammar> ::= <item> | <item> <boolean> <grammar>
+                 <item> ::= <backend_query> | <alias> | "(" <grammar> ")"
+        <backend_query> ::= <backend> "{" <query> "}"
+                <alias> ::= A:<alias_name>
+              <boolean> ::= "and not" | "and" | "xor" | "or"
 
     Given that the pyparsing library defines the grammar in a BNF-like style, 
for the details of the tokens not
-    specified above check directly the code.
+    specified above check directly the source code.
+
+    Returns:
+        pyparsing.ParserElement: the grammar parser.
+
     """
     # Boolean operators
     boolean = (pp.CaselessKeyword('and not').leaveWhitespace() | 
pp.CaselessKeyword('and') |
diff --git a/cumin/query.py b/cumin/query.py
index 9f80af6..cdd3f75 100644
--- a/cumin/query.py
+++ b/cumin/query.py
@@ -9,20 +9,37 @@
     """Cumin main query class.
 
     It has multi-query capability and allow to use a default backend, if set, 
without additional syntax.
-    If a default_backend is set in the configuration, it will try to execute 
the query string first with the default
-    backend and only if the query is not parsable with that backend will try 
to execute it with the multi-query grammar.
+    If a ``default_backend`` is set in the configuration, it will try to 
execute the query string first with the
+    default backend and only if the query is not parsable with that backend it 
will try to execute it with the
+    multi-query grammar.
 
-    When a query is executed, a ClusterShell's NodeSet with the FQDN of the 
matched hosts is returned.
+    When a query is executed, a :py:class:`ClusterShell.NodeSet.NodeSet` with 
the FQDN of the matched hosts is
+    returned.
 
-    Typical usage:
-    >>> config = cumin.Config(args.config)
-    >>> hosts = query.Query(config, logger=logger).execute(query_string)
+    Examples:
+        >>> import cumin
+        >>> from cumin.query import Query
+        >>> config = cumin.Config()
+        >>> hosts = Query(config, logger=logger).execute(query_string)
+
     """
 
     grammar = grammar()
+    """:py:class:`pyparsing.ParserElement`: Load the grammar parser only once 
in a singleton-like way."""
 
     def execute(self, query_string):
-        """Override parent class execute method to implement the multi-query 
capability."""
+        """Override parent class execute method to implement the multi-query 
capability.
+
+        :Parameters:
+            according to parent 
:py:meth:`cumin.backends.BaseQueryAggregator.execute`.
+
+        Returns:
+            ClusterShell.NodeSet.NodeSet: with the FQDNs of the matching hosts.
+
+        Raises:
+            cumin.backends.InvalidQueryError: if unable to parse the query.
+
+        """
         if 'default_backend' not in self.config:
             try:  # No default backend set, using directly the global grammar
                 return super(Query, self).execute(query_string)
@@ -47,7 +64,14 @@
         """Execute the query with the default backend, according to the 
configuration.
 
         Arguments:
-        query_string -- the query string to be parsed and executed with the 
default backend
+            query_string (str): the query string to be parsed and executed 
with the default backend.
+
+        Returns:
+            ClusterShell.NodeSet.NodeSet: with the FQDNs of the matching hosts.
+
+        Raises:
+            cumin.backends.InvalidQueryError: if unable to get the default 
backend from the registered backends.
+
         """
         for registered_backend in REGISTERED_BACKENDS.values():
             if registered_backend.name == self.config['default_backend']:
@@ -62,7 +86,15 @@
         return query.execute(query_string)
 
     def _parse_token(self, token):
-        """Required by BaseQuery."""
+        """Concrete implementation of parent abstract method.
+
+        :Parameters:
+            according to parent 
:py:meth:`cumin.backends.BaseQueryAggregator._parse_token`.
+
+        Raises:
+            cumin.backends.InvalidQueryError: on internal parsing error.
+
+        """
         if not isinstance(token, ParseResults):  # pragma: no cover - this 
should never happen
             raise InvalidQueryError('Expecting ParseResults object, got 
{type}: {token}'.format(
                 type=type(token), token=token))
@@ -95,10 +127,15 @@
     def _replace_alias(self, token_dict):
         """Replace any alias in the query in a recursive way, alias can 
reference other aliases.
 
-        Return True if a replacement was made, False otherwise. Raise 
InvalidQueryError on failure.
-
         Arguments:
-        token_dict -- the dictionary of the parsed token returned by the 
grammar parsing
+            token_dict (dict): the dictionary of the parsed token returned by 
the grammar parsing.
+
+        Returns:
+            bool: :py:data:`True` if a replacement was made, :py:data`False` 
otherwise.
+
+        Raises:
+            cumin.backends.InvalidQueryError: if unable to replace an alias.
+
         """
         if 'alias' not in token_dict:
             return False
diff --git a/cumin/tests/__init__.py b/cumin/tests/__init__.py
index de0b2a3..69f9ab5 100644
--- a/cumin/tests/__init__.py
+++ b/cumin/tests/__init__.py
@@ -11,8 +11,8 @@
     """Return the content of a fixture file.
 
     Arguments:
-    path      -- the relative path to the test's fixture directory to be 
opened.
-    as_string -- return the content as a multiline string instead of a list of 
lines [optional, default: False]
+        path: the relative path to the test's fixture directory to be opened.
+        as_string: return the content as a multiline string instead of a list 
of lines [optional, default: False]
     """
     with open(get_fixture_path(path)) as f:
         if as_string:
@@ -27,6 +27,6 @@
     """Return the absolute path of the given fixture.
 
     Arguments:
-    path      -- the relative path to the test's fixture directory.
+        path: the relative path to the test's fixture directory.
     """
     return os.path.join(_TESTS_BASE_PATH, 'fixtures', path)
diff --git a/cumin/tests/integration/test_cli.py 
b/cumin/tests/integration/test_cli.py
index 3ae90cd..90593e1 100644
--- a/cumin/tests/integration/test_cli.py
+++ b/cumin/tests/integration/test_cli.py
@@ -185,7 +185,7 @@
     """Return the expected return code based on the parameters.
 
     Arguments:
-    params -- a dictionary with all the parameters passed to the 
variant_function
+        params: a dictionary with all the parameters passed to the 
variant_function
     """
     return_value = 2
     if '-p' in params['params'] and '--global-timeout' not in params['params']:
@@ -198,7 +198,7 @@
     """Return a list of expected lines labels for global timeout-based tests.
 
     Arguments:
-    params -- a dictionary with all the parameters passed to the 
variant_function
+        params: a dictionary with all the parameters passed to the 
variant_function
     """
     expected = []
     if '--global-timeout' not in params['params']:
@@ -216,7 +216,7 @@
     """Return a list of expected lines labels for timeout-based tests.
 
     Arguments:
-    params -- a dictionary with all the parameters passed to the 
variant_function
+        params: a dictionary with all the parameters passed to the 
variant_function
     """
     expected = []
     if '-t' not in params['params']:
@@ -242,7 +242,7 @@
     """Return a list of expected lines labels for the date command based on 
parameters.
 
     Arguments:
-    params -- a dictionary with all the parameters passed to the 
variant_function
+        params: a dictionary with all the parameters passed to the 
variant_function
     """
     expected = []
     if 'ls -la /tmp/non_existing' in params['commands']:
@@ -263,7 +263,7 @@
     """Return a list of expected lines labels for the ls command based on the 
parameters.
 
     Arguments:
-    params -- a dictionary with all the parameters passed to the 
variant_function
+        params: a dictionary with all the parameters passed to the 
variant_function
     """
     expected = []
     if 'ls -la /tmp' in params['commands']:
@@ -311,7 +311,7 @@
         """Return the query for the nodes selection.
 
         Arguments:
-        nodes - a string with the ClusterShell NodeSet nodes selection
+            nodes: a string with the ClusterShell NodeSet nodes selection
         """
         if nodes is None:
             return self.all_nodes
diff --git a/cumin/tests/unit/test_cli.py b/cumin/tests/unit/test_cli.py
index 9979fb4..71ca566 100644
--- a/cumin/tests/unit/test_cli.py
+++ b/cumin/tests/unit/test_cli.py
@@ -40,7 +40,7 @@
 
 
 def test_parse_args_no_mode():
-    """If mode is not speficied with multiple commands, parsing the args 
should raise a parser error."""
+    """If mode is not specified with multiple commands, parsing the args 
should raise a parser error."""
     index = _ARGV.index('-m')
     with pytest.raises(SystemExit):
         cli.parse_args(argv=_ARGV[:index] + _ARGV[index + 1:])
diff --git a/cumin/transport.py b/cumin/transport.py
index c482075..bdca28a 100644
--- a/cumin/transport.py
+++ b/cumin/transport.py
@@ -10,12 +10,21 @@
 
     @staticmethod
     def new(config, target, logger=None):
-        """Return an instance of the worker class for the configured transport.
+        """Create a transport worker class based on the configuration 
(`factory`).
 
         Arguments:
-        config -- the configuration dictionary
-        target -- a Target instance
-        logger -- an optional logging instance [optional, default: None]
+            config (dict): the configuration dictionary.
+            target (cumin.transports.Target): a Target instance.
+            logger (logging.Logger, optional): an optional logger instance.
+
+        Returns:
+            BaseWorker: the created worker instance for the configured 
transport.
+
+        Raises:
+            cumin.CuminError: if the configuration is missing the required 
``transport`` key.
+            exceptions.ImportError: if unable to import the transport module.
+            exceptions.AttributeError: if the transport module is missing the 
required ``worker_class`` attribute.
+
         """
         if 'transport' not in config:
             raise CuminError("Missing required parameter 'transport' in the 
configuration dictionary")
diff --git a/cumin/transports/__init__.py b/cumin/transports/__init__.py
index a51347b..153f177 100644
--- a/cumin/transports/__init__.py
+++ b/cumin/transports/__init__.py
@@ -29,11 +29,11 @@
         """Command constructor.
 
         Arguments:
-        command  -- the command to execute.
-        timeout  -- the command's timeout in seconds. [optional, default: None]
-        ok_codes -- a list of exit codes to be considered successful for the 
command. The exit code 0 is considered
-                    successful by default, if this option is set it override 
it. If set to an empty list it means
-                    that any code is considered successful. [optional, 
default: None]
+            command (str): the command to execute.
+            timeout (int, optional): the command's timeout in seconds.
+            ok_codes (list, optional): a list of exit codes to be considered 
successful for the command.
+                The exit code zero is considered successful by default, if 
this option is set it override it. If set
+                to an empty list ``[]``, it means that any code is considered 
successful.
         """
         self.command = command
         self._timeout = None
@@ -46,7 +46,14 @@
             self.ok_codes = ok_codes
 
     def __repr__(self):
-        """Repr of the command, allow to instantiate a Command with the same 
properties."""
+        """Return the representation of the :py:class:`Command`.
+
+        The representation allow to instantiate a new :py:class:`Command` 
instance with the same properties.
+
+        Returns:
+            str: the representation of the object.
+
+        """
         params = ["'{command}'".format(command=self.command.replace("'", 
r'\''))]
 
         for field in ('_timeout', '_ok_codes'):
@@ -57,15 +64,27 @@
         return 'cumin.transports.Command({params})'.format(params=', 
'.join(params))
 
     def __str__(self):
-        """String representation of the command."""
+        """Return the string representation of the command.
+
+        Returns:
+            str: the string representation of the object.
+
+        """
         return self.command
 
     def __eq__(self, other):
-        """Equality operation. Allow to directly compare a Command object to 
another or a string.
+        """Equality operation. Allow to directly compare a :py:class:`Command` 
object to another or a string.
 
-        Raises ValueError if the comparing object is not an instance of 
Command or a string.
+        :Parameters:
+            according to Python's Data model :py:meth:`object.__eq__`.
 
-        Arguments: according to Python's datamodel documentation
+        Returns:
+            bool: :py:data:`True` if the `other` object is equal to this one, 
:py:data:`False` otherwise.
+
+        Raises:
+            exceptions.ValueError: if the comparing object is not an instance 
of :py:class:`Command` or a
+                :py:class:`str`.
+
         """
         if isinstance(other, str):
             other_command = other
@@ -81,25 +100,40 @@
     def __ne__(self, other):
         """Inequality operation. Allow to directly compare a Command object to 
another or a string.
 
-        Raises ValueError if the comparing object is not an instance of 
Command or a string.
+        :Parameters:
+            according to Python's Data model :py:meth:`object.__ne__`.
 
-        Arguments: according to Python's datamodel documentation
+        Returns:
+            bool: :py:data:`True` if the `other` object is different to this 
one, :py:data:`False` otherwise.
+
+        Raises:
+            exceptions.ValueError: if the comparing object is not an instance 
of :py:class:`Command` or a
+                :py:class:`str`.
+
         """
         return not self == other
 
     @property
     def timeout(self):
-        """Getter for the command's timeout property, return None if not 
set."""
+        """Timeout of the :py:class:`Command`.
+
+        :Getter:
+            Returns the current `timeout` or :py:data:`None` if not set.
+
+        :Setter:
+            :py:class:`float`, :py:class:`int`, :py:data:`None`: the `timeout` 
in seconds for the execution of the
+            `command` on each host. Both :py:class:`float` and :py:class:`int` 
are accepted and converted internally to
+            :py:class:`float`. If :py:data:`None` the `timeout` is reset to 
its default value.
+
+        Raises:
+            cumin.transports.WorkerError: if trying to set it to an invalid 
value.
+
+        """
         return self._timeout
 
     @timeout.setter
     def timeout(self, value):
-        """Setter for the command's timeout property with validation, raise 
WorkerError if not valid.
-
-        Arguments:
-        value -- the command's timeout in seconds for it's execution on each 
host. Must be a positive float or a
-                 positive integer, or None to unset it.
-        """
+        """Setter for the timeout property. The relative documentation is in 
the getter."""
         if isinstance(value, int):
             value = float(value)
 
@@ -108,7 +142,22 @@
 
     @property
     def ok_codes(self):
-        """Getter for the command's ok_codes property, return a list with only 
the element 0 if not set."""
+        """List of exit codes to be considered successful for the execution of 
the :py:class:`Command`.
+
+        :Getter:
+            Returns the current `ok_codes` or a :py:class:`list` with the 
element ``0`` if not set.
+
+        :Setter:
+            :py:class:`list[int]`, :py:data:`None`: list of exit codes to be 
considered successful for the execution of
+            the `command` on each host. Must be a :py:class:`list` of 
:py:class:`int` in the range ``0-255`` included,
+            or :py:data:`None` to unset it. The exit code ``0`` is considered 
successful by default, but it can be
+            overriden setting this property. Set it to an empty 
:py:class:`list` to consider any
+            exit code successful.
+
+        Raises:
+            cumin.transports.WorkerError: if trying to set it to an invalid 
value.
+
+        """
         ok_codes = self._ok_codes
         if ok_codes is None:
             ok_codes = [0]
@@ -117,13 +166,7 @@
 
     @ok_codes.setter
     def ok_codes(self, value):
-        """Setter for the command's ok_codes property with validation, raise 
WorkerError if not valid.
-
-        Arguments:
-        value -- the command's list of exit codes to be considered successful 
for the execution. Must be a list of
-                 integers in the range 0-255 or None to unset it. The exit 
code 0 is considered successful by default,
-                 but it can be overriden setting this property. An empty list 
is also accepted.
-        """
+        """Setter for the ok_codes property. The relative documentation is in 
the getter."""
         if value is None:
             self._ok_codes = value
             return
@@ -137,17 +180,31 @@
 
 
 class State(object):
-    """State machine for the state of a host."""
+    """State machine for the state of a host.
 
-    # Valid states indexes
+    .. attribute:: current
+
+       :py:class:`int`: the current `state`.
+
+    .. attribute:: pending, scheduled, running, success, failed, timeout
+
+        :py:class:`int`: the available valid states, according to 
:py:attr:`valid_states`.
+
+    .. attribute:: is_pending, is_scheduled, is_running, is_success, 
is_failed, is_timeout
+
+       :py:class:`bool`: :py:data:`True` if this is the current `state`, 
:py:data:`False` otherwise.
+
+    """
+
     valid_states = range(6)
-    # Valid states
+    """:py:class:`list`: valid states indexes."""
+
     pending, scheduled, running, success, failed, timeout = valid_states
+    """Valid states."""
 
-    # String representation of the valid states
     states_representation = ('pending', 'scheduled', 'running', 'success', 
'failed', 'timeout')
+    """:py:func:`tuple`: tuple with the string representations of the valid 
states."""
 
-    # Dictionary of tuples of valid states to which the transition is allowed 
from the current state
     allowed_state_transitions = {
         pending: (scheduled, ),
         scheduled: (running, ),
@@ -156,15 +213,18 @@
         failed: (),
         timeout: (),
     }
+    """:py:class:`dict`: dictionary with ``{valid state: tuple of valid 
states}`` mapping of allowed transitions for
+    any valid state."""
 
     def __init__(self, init=None):
-        """State constructor. The initial state is set to pending it not 
provided.
-
-        Raises InvalidStateError if init is an invalid state.
+        """State constructor. The initial state is set to `pending` it not 
provided.
 
         Arguments:
-        init -- the initial state from where to start. If not specified, the 
State will start in the pending state.
-                [optional, default: None]
+            init (int, optional): the initial state from where to start. The 
`pending` state will be used if not set.
+
+        Raises:
+            cumin.transports.InvalidStateError: if `init` is an invalid state.
+
         """
         if init is None:
             self._state = self.pending
@@ -177,10 +237,17 @@
     def __getattr__(self, name):
         """Attribute accessor.
 
-        Returns the current state and dynamically a bool for variables named 
'is_{valid_state_name}'.
-        Raises AttributeError otherwise.
+        :Accessible properties:
+            - `current` (:py:class:`int`): retuns the current state.
+            - `is_{valid_state_name}` (:py:class:`bool`): for each valid state 
name, returns :py:data:`True` if the
+              current state matches the state in the variable name. 
:py:data:`False` otherwise.
 
-        Arguments: according to Python's datamodel documentation
+        :Parameters:
+            according to Python's Data model :py:meth:`object.__getattr__`.
+
+        Raises:
+            exceptions.AttributeError: if the attribute name is not available.
+
         """
         if name == 'current':
             return self._state
@@ -190,19 +257,41 @@
             raise AttributeError("'State' object has no attribute 
'{name}'".format(name=name))
 
     def __repr__(self):
-        """Repr of the state, allow to instantiate a State in the same 
state."""
+        """Return the representation of the :py:class:`State`.
+
+        The representation allow to instantiate a new :py:class:`State` 
instance with the same properties.
+
+        Returns:
+            str: the representation of the object.
+
+        """
         return 'cumin.transports.State(init={state})'.format(state=self._state)
 
     def __str__(self):
-        """String representation of the state."""
+        """Return the string representation of the state.
+
+        Returns:
+            str: the string representation of the object.
+
+        """
         return self.states_representation[self._state]
 
     def __cmp__(self, other):
-        """Comparison operation. Allow to directly compare a state object to 
another or to an integer.
+        """Comparison operator.
 
-        Raises ValueError if the comparing object is not an instance of State 
or an integer.
+        Allow to directly compare a :py:class:`State` object to another or to 
an :py:class:`int`.
 
-        Arguments: according to Python's datamodel documentation
+        :Parameters:
+            according to Python's Data model :py:meth:`object.__cmp__`.
+
+        Returns:
+            int: a negative integer if `self` is lesser than `other`, zero if 
`self` is equal to `other`, a positive
+            integer if `self` is greater than `other`.
+
+        Raises:
+            exceptions.ValueError: if the comparing object is not an instance 
of :py:class:`State` or a
+                :py:class:`int`.
+
         """
         if isinstance(other, int):
             return self._state - other
@@ -214,10 +303,13 @@
     def update(self, new):
         """Transition the state from the current state to the new one, if the 
transition is allowed.
 
-        Raises StateTransitionError if the transition is not allowed, see 
allowed_state_transitions.
-
         Arguments:
-        new -- the new state to set. Only specific state transitions are 
allowed.
+            new (int): the new state to set. Only specific state transitions 
are allowed.
+
+        Raises:
+            cumin.transports.StateTransitionError: if the transition is not 
allowed, see
+                :py:attr:`allowed_state_transitions`.
+
         """
         if new not in self.valid_states:
             raise ValueError("State must be one of {valid}, got 
'{new}'".format(valid=self.valid_states, new=new))
@@ -237,13 +329,19 @@
         """Constructor, inizialize the Target with the list of hosts and 
additional parameters.
 
         Arguments:
-        hosts       -- a ClusterShell's NodeSet or a list of hosts that will 
be targeted
-        batch_size  -- set the batch size so that no more that this number of 
hosts are targeted at any given time.
-                       If greater than the number of hosts it will be 
auto-resized to the number of hosts. It must be
-                       a positive integer or None to unset it. [optional, 
default: None]
-        batch_sleep -- sleep time in seconds between the end of execution of 
one host in the batch and the start in
-                       the next host. It must be a positive float or None to 
unset it. [optional, default: None]
-        logger      -- a logging.Logger instance [optional, default: None]
+            hosts (ClusterShell.NodeSet.NodeSet, list): hosts that will be 
targeted, both
+                :py:class:`ClusterShell.NodeSet.NodeSet` and :py:class:`list` 
are accepted and converted automatically
+                to :py:class:`ClusterShell.NodeSet.NodeSet` internally.
+            batch_size (int, optional): set the batch size so that no more 
that this number of hosts are targeted
+                at any given time. If greater than the number of hosts it will 
be auto-resized to the number of hosts.
+                It must be a positive integer or :py:data:`None` to unset it.
+            batch_sleep (int, optional): sleep time in seconds between the end 
of execution of one host in the
+                batch and the start in the next host. It must be a positive 
float or None to unset it.
+            logger (logging.Logger, optional): a logger instance.
+
+        Raises:
+            cumin.transports.WorkerError: if the `hosts` parameter is invalid.
+
         """
         self.logger = logger or logging.getLogger(__name__)
 
@@ -259,16 +357,26 @@
 
     @property
     def first_batch(self):
-        """Extract the first batch of hosts to execute."""
+        """First batch of the hosts to target.
+
+        :Getter:
+            Returns a :py:class:`ClusterShell.NodeSet.NodeSet` of the first 
batch of hosts, according to the
+            `batch_size`.
+        """
         return self.hosts[:self.batch_size]
 
     def _compute_batch_size(self, batch_size, hosts):
         """Compute the batch_size based on the hosts size and return the value 
to be used.
 
         Arguments:
-        batch_size -- a positive integer to indicate the batch_size to apply 
when executing the worker or None to get
-                      its default value. If greater than the number of hosts, 
the number of hosts will be used as value.
-        hosts      -- the list of hosts to use to calculate the batch size.
+            batch_size (int, None): a positive integer to indicate the 
batch_size to apply when executing the worker or
+                :py:data:`None` to get its default value. If greater than the 
number of hosts, the number of hosts
+                will be used as value instead.
+            hosts (ClusterShell.NodeSet.NodeSet): the list of hosts to use to 
calculate the batch size.
+
+        Returns:
+            int: the effective `batch_size` to use.
+
         """
         validate_positive_integer('batch_size', batch_size)
         hosts_size = len(hosts)
@@ -287,8 +395,12 @@
         """Validate batch_sleep and return its value or a default value.
 
         Arguments:
-        batch_sleep -- a positive float indicating the sleep in seconds to 
apply between one batched host and the next,
-                       or None to get its default value.
+            batch_sleep(float, None): a positive float indicating the sleep in 
seconds to apply between one batched
+                host and the next, or :py:data:`None` to get its default value.
+
+        Returns:
+            float: the effective `batch_sleep` to use.
+
         """
         validate_positive_float('batch_sleep', batch_sleep)
         return batch_sleep or 0.0
@@ -303,9 +415,9 @@
         """Worker constructor. Setup environment variables and initialize 
properties.
 
         Arguments:
-        config -- a dictionary with the parsed configuration file
-        target -- a Target instance
-        logger -- an optional logger instance [optional, default: None]
+            config (dict): a dictionary with the parsed configuration file.
+            target (Target): a Target instance.
+            logger (logging.Logger, optional): an optional logger instance.
         """
         self.config = config
         self.target = target
@@ -324,25 +436,43 @@
 
     @abstractmethod
     def execute(self):
-        """Execute the task as configured. Return 0 on success, an int > 0 on 
failure."""
+        """Execute the task as configured.
+
+        Returns:
+            int: ``0`` on success, a positive integer on failure.
+
+        """
 
     @abstractmethod
     def get_results(self):
-        """Generator that yields tuples '(node_name, result)' with the results 
of the current execution."""
+        """Iterate over the results (`generator`).
+
+        Yields:
+            tuple: with ``(hosts, result)`` for each host(s) of the current 
execution.
+
+        """
 
     @property
     def commands(self):
-        """Getter for the commands property with a default value."""
+        """Commands for the current execution.
+
+        :Getter:
+            Returns the current `command` :py:class:`list` or an empty 
:py:class:`list` if not set.
+
+        :Setter:
+            :py:class:`list[Command]`, :py:class:`list[str]`: a 
:py:class:`list` of :py:class:`Command` objects or
+            :py:class:`str` to be executed in the hosts. The elements are 
converted to :py:class:`Command`
+            automatically.
+
+        Raises:
+            cumin.transports.WorkerError: if trying to set it with invalid 
data.
+
+        """
         return self._commands or []
 
     @commands.setter
     def commands(self, value):
-        """Setter for the commands property with validation, raise WorkerError 
if not valid.
-
-        Arguments:
-        value -- a list of Command objects or strings with the commands to be 
executed on the hosts. If a list of
-                 strings is passed, it will be automatically converted to a 
list of Command objects.
-        """
+        """Setter for the `commands` property. The relative documentation is 
in the getter."""
         if value is None:
             self._commands = value
             return
@@ -362,40 +492,63 @@
     @abstractproperty
     @property
     def handler(self):
-        """Getter for the handler property."""
+        """Get and set the `handler` for the current execution.
+
+        :Getter:
+            Returns the current `handler` or :py:data:`None` if not set.
+
+        :Setter:
+            :py:class:`str`, :py:class:`EventHandler`, :py:data:`None`: an 
event handler to be notified of the progress
+            during execution. Its interface depends on the actual transport 
chosen. Accepted values are:
+            * None => don't use an event handler (default)
+            * str => a string label to choose one of the available default 
EventHandler classes in that transport,
+            * an event handler class object (not instance)
+        """
 
     @abstractproperty
     @handler.setter
     def handler(self, value):
-        """Setter for the handler property with validation, can raise 
WorkerError if not valid.
-
-        Arguments:
-        value -- an event handler to be notified of the progress during 
execution. It's interface depends on the
-                 actual transport chosen. Accepted values are:
-                 - None => don't use an event handler (default)
-                 - str => a string label to choose one of the available 
default EventHandler classes in that transport,
-                 - an event handler class object (not instance)
-                [optional, default: None]
-        """
+        """Setter for the `handler` property. The relative documentation is in 
the getter."""
 
     @property
     def timeout(self):
-        """Getter for the global timeout property, default to 0 (unlimited) if 
not set."""
+        """Global timeout for the current execution.
+
+        :Getter:
+            int: returns the current `timeout` or ``0`` (no timeout) if not 
set.
+
+        :Setter:
+            :py:class:`int`, :py:data:`None`: timeout for the current 
execution in seconds. Must be a positive integer
+            or :py:data:`None` to reset it.
+
+        Raises:
+            cumin.transports.WorkerError: if trying to set it to an invalid 
value.
+
+        """
         return self._timeout or 0
 
     @timeout.setter
     def timeout(self, value):
-        """Setter for the global timeout property with validation, raise 
WorkerError if not valid.
-
-        Arguments:
-        value -- the global timeout in seconds for the whole execution. Must 
be a positive integer or None to unset it.
-        """
+        """Setter for the global `timeout` property. The relative 
documentation is in the getter."""
         validate_positive_integer('timeout', value)
         self._timeout = value
 
     @property
     def success_threshold(self):
-        """Getter for the success_threshold property with a default value."""
+        """Success threshold for the current execution.
+
+        :Getter:
+            float: returns the current `success_threshold` or ``1.0`` (`100%`) 
if not set.
+
+        :Setter:
+            :py:class:`float`, :py:data:`None`: The success ratio threshold 
that must be reached to consider the run
+            successful. A :py:class:`float` between ``0`` and ``1`` or 
:py:data:`None` to reset it. The specific
+            meaning might change based on the chosen transport.
+
+        Raises:
+            cumin.transports.WorkerError: if trying to set it to an invalid 
value.
+
+        """
         success_threshold = self._success_threshold
         if success_threshold is None:
             success_threshold = 1.0
@@ -404,12 +557,7 @@
 
     @success_threshold.setter
     def success_threshold(self, value):
-        """Setter for the success_threshold property with validation, raise 
WorkerError if not valid.
-
-        Arguments:
-        value -- The success ratio threshold that must be reached to consider 
the run successful. A float between 0
-                 and 1 or None. The specific meaning might change based on the 
chosen transport. [default: 1]
-        """
+        """Setter for the `success_threshold` property. The relative 
documentation is in the getter."""
         if value is not None and (not isinstance(value, float) or
                                   not (0.0 <= value <= 1.0)):  # pylint: 
disable=superfluous-parens
             raise WorkerError("success_threshold must be a float beween 0 and 
1, got '{value_type}': {value}".format(
@@ -419,11 +567,16 @@
 
 
 def validate_list(property_name, value, allow_empty=False):
-    """Helper to validate a list, raise WorkerError otherwise.
+    """Validate a list.
 
     Arguments:
-    property_name -- the name of the property to validate
-    value         -- the value to validate
+        property_name (str): the name of the property to validate.
+        value (list): the value to validate.
+        allow_empty (bool, optional): whether to consider an empty list valid.
+
+    Raises:
+        cumin.transports.WorkerError: if trying to set it to an invalid value.
+
     """
     if not isinstance(value, list):
         raise_error(property_name, 'must be a list', value)
@@ -433,34 +586,42 @@
 
 
 def validate_positive_integer(property_name, value):
-    """Helper to validate a positive integer or None, raise WorkerError 
otherwise.
+    """Validate a positive integer or :py:data:`None`.
 
     Arguments:
-    property_name -- the name of the property to validate
-    value         -- the value to validate
+        property_name (str): the name of the property to validate.
+        value (int, None): the value to validate.
+
+    Raises:
+        cumin.transports.WorkerError: if trying to set it to an invalid value.
+
     """
     if value is not None and (not isinstance(value, int) or value <= 0):
         raise_error(property_name, 'must be a positive integer or None', value)
 
 
 def validate_positive_float(property_name, value):
-    """Helper to validate a positive float or None, raise WorkerError 
otherwise.
+    """Validate a positive float or :py:data:`None`.
 
     Arguments:
-    property_name -- the name of the property to validate
-    value         -- the value to validate
+        property_name (str): the name of the property to validate.
+        value (float, None): the value to validate.
+
+    Raises:
+        cumin.transports.WorkerError: if trying to set it to an invalid value.
+
     """
     if value is not None and (not isinstance(value, float) or value <= 0):
         raise_error(property_name, 'must be a positive float or None', value)
 
 
 def raise_error(property_name, message, value):
-    """Helper to raise a WorkerError exception.
+    """Raise a :py:class:`WorkerError` exception.
 
     Arguments:
-    property_name -- the name of the property that raised the exception
-    message       -- the message to use for the exception
-    value         -- the value that raised the exception
+        property_name (str): the name of the property that raised the 
exception.
+        message (str): the message to use for the exception.
+        value (mixed): the value that raised the exception.
     """
     raise WorkerError("{property_name} {message}, got '{value_type}': 
{value}".format(
         property_name=property_name, message=message, value_type=type(value), 
value=value))
diff --git a/cumin/transports/clustershell.py b/cumin/transports/clustershell.py
index ea75518..d5ba468 100644
--- a/cumin/transports/clustershell.py
+++ b/cumin/transports/clustershell.py
@@ -19,7 +19,8 @@
     def __init__(self, config, target, logger=None):
         """Worker ClusterShell constructor.
 
-        Arguments: according to BaseWorker
+        :Parameters:
+            according to parent 
:py:meth:`cumin.transports.BaseWorker.__init__`.
         """
         super(ClusterShellWorker, self).__init__(config, target, logger)
         self.task = Task.task_self()  # Initialize a ClusterShell task
@@ -33,7 +34,13 @@
                 self.task.set_info(key, value)
 
     def execute(self):
-        """Required by BaseWorker."""
+        """Execute the commands on all the targets using the handler.
+
+        Concrete implementation of parent abstract method.
+
+        :Parameters:
+            according to parent :py:meth:`cumin.transports.BaseWorker.execute`.
+        """
         if not self.commands:
             self.logger.warning('No commands provided')
             return
@@ -72,21 +79,34 @@
         return return_value
 
     def get_results(self):
-        """Required by BaseWorker."""
+        """Get the results of the last task execution.
+
+        Concrete implementation of parent abstract method.
+
+        :Parameters:
+            according to parent 
:py:meth:`cumin.transports.BaseWorker.get_results`.
+        """
         for output, nodelist in self.task.iter_buffers():
             yield NodeSet.NodeSet.fromlist(nodelist), output
 
     @property
     def handler(self):
-        """Getter for the handler property."""
+        """Concrete implementation of parent abstract getter and setter.
+
+        Accepted values for the setter:
+        * an instance of a custom handler class derived from 
:py:class:`BaseEventHandler`.
+        * a :py:class:`str` with one of the available default handler listed 
in :py:data:`DEFAULT_HANDLERS`.
+
+        The event handler is mandatory for this transport.
+
+        :Parameters:
+            according to parent :py:attr:`cumin.transports.BaseWorker.handler`.
+        """
         return self._handler
 
     @handler.setter
     def handler(self, value):
-        """Required by BaseTask.
-
-        The available default handlers are defined in DEFAULT_HANDLERS.
-        """
+        """Setter for the `handler` property. The relative documentation is in 
the getter."""
         if isinstance(value, type) and issubclass(value, BaseEventHandler):
             self._handler = value
         elif value in DEFAULT_HANDLERS:
@@ -106,8 +126,8 @@
         """Node class constructor with default values.
 
         Arguments:
-        name     -- the hostname of the node.
-        commands -- a list of Command objects to be executed on the node.
+            name (str): the hostname of the node.
+            commands (list): a list of :py:class:`cumin.transports.Command` 
objects to be executed on the node.
         """
         self.name = name
         self.commands = commands
@@ -116,27 +136,27 @@
 
 
 class BaseEventHandler(Event.EventHandler):
-    """ClusterShell event handler extension base class.
+    """ClusterShell event handler base class.
 
-    Inherit from ClusterShell's EventHandler class and define a base 
EventHandler class to be used in Cumin.
-    It can be subclassed to generate custom EventHandler classes while taking 
advantage of some common
+    Inherit from :py:class:`ClusterShell.Event.EventHandler` class and define 
a base `EventHandler` class to be used
+    in Cumin. It can be subclassed to generate custom `EventHandler` classes 
while taking advantage of some common
     functionalities.
     """
 
-    short_command_length = 35  # For logging and printing the commands are 
shortened to reach at most this length
+    short_command_length = 35
+    """:py:class:`int`: the length to which a command should be shortened in 
various outputs."""
 
     def __init__(self, target, commands, success_threshold=1.0, logger=None, 
**kwargs):
         """Event handler ClusterShell extension constructor.
 
-        If subclasses defines a self.pbar_ko tqdm progress bar, it will be 
updated on timeout.
+        If subclasses defines a ``self.pbar_ko`` `tqdm` progress bar, it will 
be updated on timeout.
 
         Arguments:
-        target            -- a Target instance.
-        commands          -- the list of Command objects that has to be 
executed on the nodes.
-        success_threshold -- the success threshold, a float between 0 and 1, 
to consider the execution successful.
-                             [optional, default: 1.0]
-        **kwargs          -- additional keyword arguments that might be used 
by classes that extend this base class.
-                             [optional]
+            target (cumin.transports.Target): a Target instance.
+            commands (list): the list of Command objects that has to be 
executed on the nodes.
+            success_threshold (float, optional): the success threshold, a 
:py:class:`float` between ``0`` and ``1``,
+                to consider the execution successful.
+            **kwargs (optional): additional keyword arguments that might be 
used by derived classes.
         """
         super(BaseEventHandler, self).__init__()
         self.success_threshold = success_threshold
@@ -171,17 +191,18 @@
         """Additional method called at the end of the whole execution, useful 
for reporting and final actions.
 
         Arguments:
-        task -- a ClusterShell Task instance
+            task (ClusterShell.Task.Task): a ClusterShell Task instance.
         """
         raise NotImplementedError
 
     def on_timeout(self, task):
-        """Callback called by the ClusterShellWorker when a Task.TimeoutError 
is raised.
+        """Update the state of the nodes and the timeout counter.
 
-        The whole execution timed out, update the state of the nodes and the 
timeout counter accordingly.
+        Callback called by the :py:class:`ClusterShellWorker` when a 
:py:exc:`ClusterShell.Task.TimeoutError` is
+        raised. It means that the whole execution timed out.
 
         Arguments:
-        task -- a ClusterShell Task instance
+            task (ClusterShell.Task.Task): a ClusterShell Task instance.
         """
         num_timeout = task.num_timeout()
         self.logger.error('global timeout was triggered while {num} nodes were 
executing a command'.format(
@@ -207,9 +228,10 @@
     def ev_pickup(self, worker):
         """Command execution started on a node, remove the command from the 
node's queue.
 
-        This callback is triggered by ClusterShell for each node when it 
starts executing a command.
+        This callback is triggered by the `ClusterShell` library for each node 
when it starts executing a command.
 
-        Arguments: according to EventHandler interface
+        :Parameters:
+            according to parent 
:py:meth:`ClusterShell.Event.EventHandler.ev_pickup`.
         """
         self.logger.debug("node={node}, command='{command}'".format(
             node=worker.current_node, command=worker.command))
@@ -237,7 +259,8 @@
 
         This callback is triggered by ClusterShell for each node when output 
is available.
 
-        Arguments: according to EventHandler interface
+        :Parameters:
+            according to parent 
:py:meth:`ClusterShell.Event.EventHandler.ev_read`.
         """
         if self.deduplicate_output:
             return
@@ -249,7 +272,8 @@
 
         This callback is triggered by ClusterShell when the execution has 
timed out.
 
-        Arguments: according to EventHandler interface
+        :Parameters:
+            according to parent 
:py:meth:`ClusterShell.Event.EventHandler.ev_timeout`.
         """
         delta_timeout = worker.task.num_timeout() - self.counters['timeout']
         self.logger.debug("command='{command}', delta_timeout={num}".format(
@@ -269,14 +293,16 @@
         worker.task.timer(self.target.batch_sleep, worker.eh)
 
     def _get_log_message(self, num, message, nodes=None):
-        """Helper to get a pre-formatted message suitable for logging or 
printing.
-
-        Returns a tuple of two strings: a logging message, the affected nodes 
in NodeSet format
+        """Get a pre-formatted message suitable for logging or printing.
 
         Arguments:
-        num     - the number of affecte nodes
-        message - the message to print
-        nodes   - the list of nodes affected [optional, default: None]
+            num (int): the number of affecte nodes.
+            message (str): the message to print.
+            nodes (list, optional): the list of nodes affected.
+
+        Returns:
+            tuple: a tuple of ``(logging message, NodeSet of the affected 
nodes)``.
+
         """
         if nodes is None:
             nodes_string = ''
@@ -292,32 +318,37 @@
         return (log_message, str(nodes_string))
 
     def _print_report_line(self, message, color=colorama.Fore.RED, 
nodes_string=''):  # pylint: disable=no-self-use
-        """Helper to print a tqdm-friendly colored status line with 
success/failure ratio and optional list of nodes.
+        """Print a tqdm-friendly colored status line with success/failure 
ratio and optional list of nodes.
 
         Arguments:
-        message      -- the message to print
-        color        -- the message color [optional, default: 
colorama.Fore.RED]
-        nodes_string -- the string representation of the affected nodes 
[optional, default: '']
+            message (str): the message to print.
+            color (str, optional): the message color.
+            nodes_string (str, optional): the string representation of the 
affected nodes.
         """
         tqdm.write('{color}{message}{nodes_color}{nodes_string}{reset}'.format(
             color=color, message=message, nodes_color=colorama.Fore.CYAN,
             nodes_string=nodes_string, reset=colorama.Style.RESET_ALL), 
file=sys.stderr)
 
     def _get_short_command(self, command):
-        """Return a shortened representation of a command omitting the central 
part.
+        """Return a shortened representation of a command omitting the central 
part, if it's too long.
 
         Arguments:
-        command - the command to be shortened
+            command (str): the command to be shortened.
+
+        Returns:
+            str: the short command.
+
         """
         sublen = (self.short_command_length - 3) // 2  # The -3 is for the 
ellipsis
         return (command[:sublen] + '...' + command[-sublen:]) if len(command) 
> self.short_command_length else command
 
     def _commands_output_report(self, buffer_iterator, command=None):
-        """Helper to print the commands output in a colored and tqdm-friendly 
way.
+        """Print the commands output in a colored and tqdm-friendly way.
 
         Arguments:
-        buffer_iterator - any ClusterShell object that implements 
iter_buffers() like Task and Worker objects.
-        command         - command the output is referring to [optional, 
default: None]
+            buffer_iterator (mixed): any `ClusterShell` object that implements 
``iter_buffers()`` like
+                :py:class:`ClusterShell.Task.Task` and all the `Worker` 
objects.
+            command (str, optional): the command the output is referring to.
         """
         if not self.deduplicate_output:
             tqdm.write(colorama.Fore.BLUE + '================' + 
colorama.Style.RESET_ALL, file=sys.stdout)
@@ -345,7 +376,7 @@
         tqdm.write(colorama.Fore.BLUE + message + colorama.Style.RESET_ALL, 
file=sys.stdout)
 
     def _global_timeout_nodes_report(self):
-        """Helper to print the nodes that were caught by the global timeout in 
a colored and tqdm-friendly way."""
+        """Print the nodes that were caught by the global timeout in a colored 
and tqdm-friendly way."""
         if not self.global_timedout:
             return
 
@@ -362,10 +393,11 @@
         self._print_report_line(not_run_message, nodes_string=not_run_nodes)
 
     def _failed_commands_report(self, filter_command_index=-1):
-        """Helper to print the nodes that failed to execute commands in a 
colored and tqdm-friendly way.
+        """Print the nodes that failed to execute commands in a colored and 
tqdm-friendly way.
 
         Arguments:
-        filter_command - print only the nodes that failed to execute this 
specific command [optional, default: None]
+            filter_command_index (int, optional): print only the nodes that 
failed to execute the command specified by
+                this command index.
         """
         for state in (State.failed, State.timeout):
             failed_commands = defaultdict(list)
@@ -385,7 +417,11 @@
                 self._print_report_line(log_message, nodes_string=nodes_string)
 
     def _success_nodes_report(self, command=None):
-        """Helper to print how many nodes succesfully executed all commands in 
a colored and tqdm-friendly way."""
+        """Print how many nodes succesfully executed all commands in a colored 
and tqdm-friendly way.
+
+        Arguments:
+            command (str, optional): the command the report is referring to.
+        """
         if self.global_timedout and command is None:
             num = sum(1 for node in self.nodes.itervalues() if 
node.state.is_success and
                       node.running_command_index == (len(self.commands) - 1))
@@ -432,21 +468,23 @@
     """Custom ClusterShell event handler class that execute commands 
synchronously.
 
     The implemented logic is:
-    - execute command #N on all nodes where command #N-1 was successful 
according to batch_size
-    - the success ratio is checked at each command completion on every node, 
and will abort if not met, however
-      nodes already scheduled for execution with ClusterShell will execute the 
command anyway. The use of the
-      batch_size allow to control this aspect.
-    - if the execution of command #N is completed and the success ratio is 
greater than the success threshold,
-      re-start from the top with N=N+1
+
+    * execute command `#N` on all nodes where command #`N-1` was successful 
according to `batch_size`.
+    * the success ratio is checked at each command completion on every node, 
and will abort if not met, however
+      nodes already scheduled for execution with `ClusterShell` will execute 
the command anyway. The use of the
+      `batch_size` allow to control this aspect.
+    * if the execution of command `#N` is completed and the success ratio is 
greater than the success threshold,
+      re-start from the top with `N=N+1`.
 
     The typical use case is to orchestrate some operation across a fleet, 
ensuring that each command is completed by
     enough nodes before proceeding with the next one.
     """
 
     def __init__(self, target, commands, success_threshold=1.0, logger=None, 
**kwargs):
-        """Custom ClusterShell synchronous event handler constructor.
+        """Define a custom ClusterShell event handler to execute commands 
synchronously.
 
-        Arguments: according to BaseEventHandler interface
+        :Parameters:
+            according to parent :py:meth:`BaseEventHandler.__init__`.
         """
         super(SyncEventHandler, self).__init__(
             target, commands, success_threshold=success_threshold, 
logger=logger, **kwargs)
@@ -460,8 +498,7 @@
         Executed at the start of each command.
 
         Arguments:
-        schedule -- boolean to decide if the next command should be sent to 
ClusterShell for execution or not.
-                    [optional, default: False]
+            schedule (bool, optional): whether the next command should be sent 
to ClusterShell for execution or not.
         """
         self.counters['success'] = 0
 
@@ -497,6 +534,10 @@
         """Command terminated, print the result and schedule the next command 
if criteria are met.
 
         Executed at the end of each command inside a lock.
+
+        Returns:
+            bool: :py:data:`True` if the next command should be scheduled, 
:py:data:`False` otherwise.
+
         """
         self._commands_output_report(Task.task_self(), 
command=self.commands[self.current_command_index].command)
 
@@ -526,22 +567,23 @@
         return True
 
     def on_timeout(self, task):
-        """Callback called by the ClusterShellWorker when a Task.TimeoutError 
is raised.
+        """Override parent class `on_timeout` method to run `end_command`.
 
-        Arguments: according to BaseEventHandler interface
+        :Parameters:
+            according to parent :py:meth:`BaseEventHandler.on_timeout`.
         """
         super(SyncEventHandler, self).on_timeout(task)
         self.end_command()
 
     def ev_hup(self, worker):
-        """Command execution completed.
+        """Command execution completed on a node.
 
         This callback is triggered by ClusterShell for each node when it 
completes the execution of a command.
-
         Update the progress bars and keep track of nodes based on the 
success/failure of the command's execution.
         Schedule a timer for further decisions.
 
-        Arguments: according to EventHandler interface
+        :Parameters:
+            according to parent 
:py:meth:`ClusterShell.Event.EventHandler.ev_hup`.
         """
         self.logger.debug("node={node}, rc={rc}, command='{command}'".format(
             node=worker.current_node, rc=worker.current_rc, 
command=worker.command))
@@ -570,9 +612,10 @@
     def ev_timer(self, timer):
         """Schedule the current command on the next node or the next command 
on the first batch of nodes.
 
-        This callback is triggered by ClusterShell when a scheduled 
Task.timer() goes off.
+        This callback is triggered by `ClusterShell` when a scheduled 
`Task.timer()` goes off.
 
-        Arguments: according to EventHandler interface
+        :Parameters:
+            according to parent 
:py:meth:`ClusterShell.Event.EventHandler.ev_timer`.
         """
         success_ratio = 1 - (float(self.counters['failed'] + 
self.counters['timeout']) / self.counters['total'])
 
@@ -639,9 +682,10 @@
             self.start_command(schedule=True)
 
     def close(self, task):
-        """Print a final summary report line.
+        """Concrete implementation of parent abstract method to print the 
success nodes report.
 
-        Arguments: according to BaseEventHandler interface
+        :Parameters:
+            according to parent 
:py:meth:`cumin.transports.BaseEventHandler.close`.
         """
         self._success_nodes_report()
 
@@ -650,21 +694,23 @@
     """Custom ClusterShell event handler class that execute commands 
asynchronously.
 
     The implemented logic is:
-    - execute on all nodes independently every command in a sequence, aborting 
the execution on that node if any
+
+    * execute on all nodes independently every command in a sequence, aborting 
the execution on that node if any
       command fails.
-    - The success ratio is checked at each node completion (either because it 
completed all commands or aborted
+    * The success ratio is checked at each node completion (either because it 
completed all commands or aborted
       earlier), however nodes already scheduled for execution with 
ClusterShell will execute the commands anyway. The
       use of the batch_size allows to control this aspect.
-    - if the success ratio is met, schedule the execution of all commands to 
the next node.
+    * if the success ratio is met, schedule the execution of all commands to 
the next node.
 
     The typical use case is to execute read-only commands to gather the status 
of a fleet without any special need of
     orchestration between the nodes.
     """
 
     def __init__(self, target, commands, success_threshold=1.0, logger=None, 
**kwargs):
-        """Custom ClusterShell asynchronous event handler constructor.
+        """Define a custom ClusterShell event handler to execute commands 
asynchronously between nodes.
 
-        Arguments: according to BaseEventHandler interface
+        :Parameters:
+            according to parent :py:meth:`BaseEventHandler.__init__`.
         """
         super(AsyncEventHandler, self).__init__(
             target, commands, success_threshold=success_threshold, 
logger=logger, **kwargs)
@@ -680,11 +726,11 @@
         """Command execution completed on a node.
 
         This callback is triggered by ClusterShell for each node when it 
completes the execution of a command.
+        Enqueue the next command if the success criteria are met, track the 
failure otherwise. Update the progress
+        bars accordingly.
 
-        Enqueue the next command if the success criteria are met, track the 
failure otherwise
-        Update the progress bars accordingly
-
-        Arguments: according to EventHandler interface
+        :Parameters:
+            according to parent 
:py:meth:`ClusterShell.Event.EventHandler.ev_hup`.
         """
         self.logger.debug("node={node}, rc={rc}, command='{command}'".format(
             node=worker.current_node, rc=worker.current_rc, 
command=worker.command))
@@ -724,9 +770,10 @@
     def ev_timer(self, timer):
         """Schedule the current command on the next node or the next command 
on the first batch of nodes.
 
-        This callback is triggered by ClusterShell when a scheduled 
Task.timer() goes off.
+        This callback is triggered by `ClusterShell` when a scheduled 
`Task.timer()` goes off.
 
-        Arguments: according to EventHandler interface
+        :Parameters:
+            according to parent 
:py:meth:`ClusterShell.Event.EventHandler.ev_timer`.
         """
         success_ratio = 1 - (float(self.counters['failed'] + 
self.counters['timeout']) / self.counters['total'])
 
@@ -755,9 +802,10 @@
             self.logger.debug('No more nodes left')
 
     def close(self, task):
-        """Properly close all progress bars and print results.
+        """Concrete implementation of parent abstract method to print the 
nodes reports and close progress bars.
 
-        Arguments: according to BaseEventHandler interface
+        :Parameters:
+            according to parent 
:py:meth:`cumin.transports.BaseEventHandler.close`.
         """
         self._commands_output_report(task)
 
@@ -779,6 +827,8 @@
             self.return_value = 1
 
 
-# Required by the auto-loader in the cumin.transport.Transport factory
 worker_class = ClusterShellWorker  # pylint: disable=invalid-name
-DEFAULT_HANDLERS = {'sync': SyncEventHandler, 'async': AsyncEventHandler}  # 
Available default EventHandler classes
+"""Required by the transport auto-loader in 
:py:meth:`cumin.transport.Transport.new`."""
+
+DEFAULT_HANDLERS = {'sync': SyncEventHandler, 'async': AsyncEventHandler}
+"""dict: mapping of available default event handlers for 
:py:class:`ClusterShellWorker`."""
diff --git a/prospector.yaml b/prospector.yaml
index cf93a96..d0b8c5e 100644
--- a/prospector.yaml
+++ b/prospector.yaml
@@ -15,9 +15,13 @@
     max-line-length: 120
 
 pep257:
+  explain: true
+  source: true
   disable:
     - D203  # 1 blank line required before class docstring, D211 (after) is 
enforce instead
     - D213  # Multi-line docstring summary should start at the second line, 
D212 (first line) is enforced instead
+    - D406  # Section name should end with a newline, incompatible with Google 
Style Python Docstrings
+    - D407  # Missing dashed underline after section, incompatible with Google 
Style Python Docstrings
 
 pylint:
   disable:

-- 
To view, visit https://gerrit.wikimedia.org/r/382479
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ib46a063964d5701f365c0fe3225854246c643f6b
Gerrit-PatchSet: 3
Gerrit-Project: operations/software/cumin
Gerrit-Branch: master
Gerrit-Owner: Volans <[email protected]>
Gerrit-Reviewer: Faidon Liambotis <[email protected]>
Gerrit-Reviewer: Gehel <[email protected]>
Gerrit-Reviewer: Giuseppe Lavagetto <[email protected]>
Gerrit-Reviewer: Volans <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to