Index: conf/taskotron.yaml.example
===================================================================
--- conf/taskotron.yaml.example
+++ conf/taskotron.yaml.example
@@ -67,15 +67,18 @@
 ## How long (in minutes) should we wait before allowing consequent test to
 ## re-post a 'FAILED' comment into Bodhi once again.
 ## By default 3 days (3*24*60 = 4320).
-#bodhi_email_failed_span: 4320
+#bodhi_posting_comments_span: 4320
 
 
 ## ==== PATHS section ====
 ## Location of various pieces of the project.
 
 ## The location of log files for Taskotron
 #logdir: /var/log/taskotron
 
+## The location of temporary files for Taskotron
+#tmpdir: /var/tmp/taskotron
+
 
 ## ==== SECRETS section ====
 ## All login credentials and other secrets are here. If you add some secret
Index: docs/source/index.rst
===================================================================
--- docs/source/index.rst
+++ docs/source/index.rst
@@ -5,9 +5,9 @@
 What is libtaskotron?
 =====================
 
-Libtaskotron is one of the core components which make up the `taskotron`_
-system. Libtaskotron is responsible for running tasks written in the
-:ref:`taskyaml_section`.
+Libtaskotron is one of the core components which make up the `Taskotron`_
+system. Libtaskotron is responsible for running tasks described in the
+:doc:`taskyaml` section.
 
 While libtaskotron was designed for use with `Fedora <http://fedoraproject.org>`_,
 the Fedora specific parts are isolated and the core should be usable with any
@@ -20,21 +20,20 @@
 Support
 =======
 Please direct questions and comments to either the `Fedora QA Devel List
-<https://admin.fedoraproject.org/mailman/listinfo/qa-devel>`_ or the `#fedora-qa`
+<https://admin.fedoraproject.org/mailman/listinfo/qa-devel>`_ or the *#fedora-qa*
 IRC channel on `Freenode <http://freenode.net/>`_.
 
 
 
-.. _taskotron: https://fedoraproject.org/wiki/Taskotron
+.. _Taskotron: https://fedoraproject.org/wiki/Taskotron
 
 Contents
 ========
 
 .. toctree::
    :maxdepth: 1
 
    devguide
-   writing_tasks
    library
    taskyaml
 
Index: docs/source/library.rst
===================================================================
--- docs/source/library.rst
+++ docs/source/library.rst
@@ -11,18 +11,72 @@
 include directives or task yaml details.
 
 
-runner (one function with sphinx-style docstrings)
-==================================================
+bodhi_utils
+===========
 
-.. automodule:: libtaskotron.runner
+.. automodule:: libtaskotron.bodhi_utils
    :members:
 
-check (no sphinx-style docstrings)
-==================================
+check
+=====
 
 .. automodule:: libtaskotron.check
    :members:
 
+config
+======
+
+.. automodule:: libtaskotron.config
+   :members:
+
+config_defaults
+===============
+
+.. automodule:: libtaskotron.config_defaults
+   :members:
+
+exceptions
+==========
+
+.. automodule:: libtaskotron.exceptions
+   :members:
+
+file_utils
+==========
+
+.. automodule:: libtaskotron.file_utils
+   :members:
+
+koji_utils
+==========
+
+.. automodule:: libtaskotron.koji_utils
+   :members:
+
+logger
+======
+
+.. automodule:: libtaskotron.logger
+   :members:
+
+python_utils
+============
+
+.. automodule:: libtaskotron.python_utils
+   :members:
+
+rpm_utils
+=========
+
+.. automodule:: libtaskotron.rpm_utils
+   :members:
+
+runner
+======
+
+.. automodule:: libtaskotron.runner
+   :members:
+
 yumrepoinfo
 ===========
 
Index: libtaskotron/bodhi_utils.py
===================================================================
--- libtaskotron/bodhi_utils.py
+++ libtaskotron/bodhi_utils.py
@@ -15,13 +15,14 @@
 class BodhiUtils(object):
     '''Helper Bodhi methods.
 
-    Note: You can access the standard BodhiClient as self.client.
+    :ivar fedora.client.BodhiClient client: Bodhi client instance
     '''
 
     def __init__(self, client=None):
         '''Create a new BodhiUtils instance.
-        @param client custom BodhiClient instance. If None, a default
-                      BodhiClient instance is used.
+
+        :param client: custom :class:`BodhiClient` instance. If ``None``, a
+                       default BodhiClient instance is used.
         '''
 
         taskotron_config = config.get_config()
@@ -41,12 +42,15 @@
     def query_update(self, package):
         '''Get the last Bodhi update matching criteria.
 
-           @param package package NVR or package name or update title or
-                          update ID
-                          Note: Only NVR allowed, not NEVR. See
-                          https://fedorahosted.org/bodhi/ticket/592.
-           @return most recent Bodhi update object matching criteria, or None
-                   when no update is found.
+           :param str package: package NVR or package name or update title or
+                               update ID
+
+                               .. note::
+                                Only NVR allowed, not NEVR. See
+                                https://fedorahosted.org/bodhi/ticket/592.
+           :return: most recent Bodhi update object matching criteria, or
+                    ``None`` when no update is found.
+           :rtype: :class:`bunch.Bunch`
         '''
         log.debug('Searching Bodhi updates for: %s', package)
         res = self.client.query(package=package, limit=1)
@@ -59,28 +63,33 @@
     def build2update(self, builds, strict=False):
         '''Find matching Bodhi updates for provided builds.
 
-        @param builds builds to search for; iterable of strings in N(E)VR
-                      format (foo-1.2-3.fc20 or foo-4:1.2-3.fc20)
-        @param strict if False, incomplete Bodhi updates are allowed.
-                      If True, every Bodhi update will be compared with the set
-                      of provided builds. If there is an Bodhi update which
-                      contains builds not provided in @param builds, that update
-                      is marked as incomplete and removed from the result - i.e.
-                      all builds from @param builds that were part of this
-                      incomplete update are placed in the second dictionary of
-                      the result tuple.
-        @return tuple of two dictionaries:
-                * The first dict provides mapping between builds and their
-                  updates where no error occured.
-                  {build (string): Bodhi update (Bunch)}
-                * The second dict provides mapping between builds and their
-                  updates where some error occured.
-                  {build (string): Bodhi update (Bunch) or None}
-                  The value is None if the matching Bodhi update could not be
-                  found (the only possible cause of failure if
-                  @param strict=False). Or the value is a Bodhi update that was
-                  incomplete (happens only if @param strict=True).
-        @raise TaskotronValueError if @param builds type is incorrect
+        :param builds: builds to search for in N(E)VR format (``foo-1.2-3.fc20``
+                       or ``foo-4:1.2-3.fc20``)
+        :type builds: iterable of str
+        :param bool strict: if ``False``, incomplete Bodhi updates are allowed.
+                            If ``True``, every Bodhi update will be compared
+                            with the set of provided builds. If there is an
+                            Bodhi update which contains builds not provided in
+                            ``builds``, that update is marked as incomplete and
+                            removed from the result - i.e. all builds from
+                            ``builds`` that were part of this incomplete update
+                            are placed in the second dictionary of the result
+                            tuple.
+        :return: a tuple of two dictionaries:
+
+                 * The first dict provides mapping between ``builds`` and their
+                   updates where no error occured.
+
+                   ``{build (string): Bodhi update (Bunch)}``
+                 * The second dict provides mapping between ``builds`` and their
+                   updates where some error occured. The value is ``None`` if
+                   the matching Bodhi update could not be found (the only
+                   possible cause of failure if ``strict=False``). Or the value
+                   is a Bodhi update that was incomplete (happens only if
+                   ``strict=True``).
+
+                   ``{build (string): Bodhi update (Bunch) or None}``
+        :raise TaskotronValueError: if ``builds`` type is incorrect
         '''
         # validate input params
         if not python_utils.listlike(builds):
Index: libtaskotron/check.py
===================================================================
--- libtaskotron/check.py
+++ libtaskotron/check.py
@@ -31,57 +31,62 @@
     builds, updates, etc). That's when this class comes very handy (it can be
     used even for the simple use cases, of course).
 
-    @classattr outcome_priority a tuple of outcome keywords sorted by priority
-                                from the least important to the most important
-    @attr item a string describing the item being tested; for example a build
-               NVR ('vpnc-0.5-1.fc20'), update ID ('FEDORA-2014-3309') or a
-               repository name ('f20-updates')
-    @attr report_type a definition of the type of the object being checked; for
-                      example 'a Koji build', 'a Bodhi update' or 'a yum
-                      repository name'. The allowed values are attributes in
-                      @class ReportType. You don't have to fill this in (or you
-                      can provide a custom string value), but the reporting
-                      directives react only to the known types (you can still
-                      find it in ResultsDB, though).
-    @attr outcome a keyword specifying the final outcome of the check. Available
-                  outcome keywords:
-                  * PASSED - everything went well
-                  * INFO - everything went well, but there is some important
-                           information that needs to be pointed out
-                  * FAILED - the item in question fails the check
-                  * NEEDS_INSPECTION - the outcome can't be determined and a
-                                       manual inspection is needed
-                  * ABORTED - the check aborted itself because of some
-                              unexpected problems, i.e. a necessary network
-                              server is not reachable. Running this check with
-                              the same arguments later can help to mitigate
-                              this problem.
-                  * CRASHED - the check crashed and did not provide usable
-                              results
-                 If no outcome is set, this attribute returns NEEDS_INSPECTION.
-    @attr summary a few words or one-sentence summary about the result of the
-                  check run (e.g. '5 WARNINGS, 1 ERROR' for an rpmlint result).
-                  Should not unnecessarily duplicate self.item or self.outcome,
-                  if possible.
-    @attr output output from the check run (a list of strings). You can easily
-                 populate this by using @method store(), or you can modify it
-                 directly (for example filter out some messages you don't want
-                 to see in the check output).
-    @attr keyvals all key-value pairs in this dictionary are stored in ResultsDB
-                  as 'extra data'. This can be used to store e.g. a compose id
-                  or a kernel version, if that information is important for
-                  querying the results. Keep it as short as possible.
+    :cvar outcome_priority: a tuple of :attr:`outcome` keywords sorted by
+                            priority from the least important to the most
+                            important
+    :ivar str item: a description the item being tested; for example a build NVR
+                    (``vpnc-0.5-1.fc20``), update ID (``FEDORA-2014-3309``) or a
+                    repository name (``f20-updates``)
+    :ivar str report_type: a definition of the type of the object being checked;
+                           for example 'a Koji build', 'a Bodhi update' or 'a
+                           yum repository name'. The allowed values are
+                           attributes in :class:`ReportType`. You don't have to
+                           fill this in (or you can provide a custom string
+                           value), but the reporting directives react only to
+                           the known types (you can always find it in ResultsDB,
+                           though).
+    :ivar str outcome: a keyword specifying the final outcome of the check.
+                       Available outcome keywords:
+
+                       * PASSED - everything went well
+                       * INFO - everything went well, but there is some
+                         important information that needs to be pointed out
+                       * FAILED - the item in question fails the check
+                       * NEEDS_INSPECTION - the outcome can't be determined and
+                         a manual inspection is needed
+                       * ABORTED - the check aborted itself because of some
+                         unexpected problems, i.e. a necessary network server is
+                         not reachable. Running this check with the same
+                         arguments later can help to mitigate this problem.
+                       * CRASHED - the check crashed and did not provide usable
+                         results
+
+                       If no outcome has been set, this attribute returns
+                       NEEDS_INSPECTION.
+
+                       Raises :class:`TaskotronValueError` if you try to assign
+                       an unknown keyword.
+    :ivar str summary: a few words or one-sentence summary about the result of
+                       the check run (e.g. ``5 WARNINGS, 1 ERROR`` for an
+                       rpmlint result). Should not unnecessarily duplicate
+                       :attr:`item` or :attr:`outcome`, if possible.
+    :ivar output: output from the check run (a list of strings). You can easily
+                  populate this by using @method store(), or you can modify it
+                  directly (for example filter out some messages you don't want
+                  to see in the check output).
+    :ivar dict keyvals: all key-value pairs in this dictionary are stored in
+                        ResultsDB as 'extra data'. This can be used to store
+                        e.g. a compose id or a kernel version, if that
+                        information is important for querying the results. Keep
+                        it as short as possible.
     '''
 
     outcome_priority = ('PASSED', 'INFO', 'FAILED', 'NEEDS_INSPECTION',
                         'ABORTED', 'CRASHED')
 
 
     def __init__(self, item, report_type=None, outcome=None, summary='',
                  output=None, keyvals=None):
-        '''Create new CheckDetail.
-        For param descriptions see class docstring.'''
-
         # validate input
         if output is not None and not python_utils.listlike(output):
             raise exc.TaskotronValueError("'output' parameter must be a list. "
@@ -102,55 +107,52 @@
 
     @property
     def outcome(self):
-        '''See class documentation.'''
         return self._outcome or 'NEEDS_INSPECTION'
 
 
     @outcome.setter
     def outcome(self, value):
-        '''See class documentation.'''
         if value not in self.outcome_priority:
             raise exc.TaskotronValueError('Unknown outcome keyword: %s' % value)
         self._outcome = value
 
 
     def update_outcome(self, outcome):
-        '''Update self.outcome with the provided value only and only if it has
-        a higher priority than its current value (according to
-        self.outcome_priority). Otherwise this call does nothing.
+        '''Update :attr:`outcome` with the provided value only and only if it
+        has a higher priority than its current value (according to
+        :attr:`outcome_priority`). Otherwise this call does nothing.
 
         This is useful if your check performs a number of 'sub-tasks', each
         passing or failing, and you want to final outcome to be the worst/most
-        serious one of those. You can just keep calling self.update_outcome()
-        and the highest priority outcome type will be stored at self.outcome at
-        the end.
-
-        @param outcome the outcome value to assign to self.outcome if it has a
-                       higher priority than its current value. Handles None
-                       values gracefully (no action).
-        @throw TaskotronValueError if any of the outcome keywords are not
-                                   specified in @classatr outcome_priority'''
+        serious one of those. You can just keep calling :meth:`update_outcome`
+        and the highest priority outcome type will be stored at :attr:`outcome`
+        at the end.
+
+        :param str outcome: the outcome value to assign to :attr:`outcome` if it
+                            has a higher priority than its current value.
+                            Handles ``None`` values gracefully (no action).
+        :raise TaskotronValueError: if any of the outcome keywords are not
+                                    specified in :attr:`outcome_priority`'''
         if self.cmp_outcome(outcome, self._outcome) > 0:
             self.outcome = outcome
 
 
     def broken(self):
-        '''Tells whether the check outcome is set to one of the broken states
-        (i.e. ABORTED or CRASHED).'''
+        '''Tells whether the check :attr:`outcome` is set to one of the broken
+        states (i.e. ABORTED or CRASHED).'''
         return self.outcome in ['ABORTED', 'CRASHED']
 
 
     def store(self, message, printout=True):
-        '''Add a string to the CheckDetail output, and print it optionally as
-        well.
+        '''Add a string to the :attr:`output`, and print it optionally as well.
 
         This is just a convenience method for most common use case, you can
-        of course always access and modify self.output directly, and print or
+        of course always access and modify :attr:`output` directly, and print or
         use logging facilities directly as well. This combines both into a
         single call.
 
-        @param message a string to store in self.output
-        @param printout whether to print to standard output or not
+        :param str message: a string to store in :attr:`output`
+        :param bool printout: whether to print to standard output or not
         '''
 
         self.output.append(message)
@@ -160,13 +162,13 @@
 
     @classmethod
     def cmp_outcome(cls, outcome1, outcome2):
-        '''Compare two outcomes according to self.outcome_priority and return
-        -1/0/1 if outcome1 has lower/equal/higher priority than outcome2.
+        '''Compare two outcomes according to :attr:`outcome_priority` and return
+        -1/0/1 if ``outcome1`` has lower/equal/higher priority than ``outcome2``.
 
-        @param outcome1 a outcome keyword to compare
-        @param outcome2 a outcome keyword to compare
-        @throw TaskotronValueError if any of the outcome keywords are not
-                                   specified in @classatr outcome_priority
+        :param str outcome1: an outcome keyword to compare
+        :param str outcome2: an outcome keyword to compare
+        :raise TaskotronValueError: if any of the outcome keywords are not
+                                    specified in :attr:`outcome_priority`
         '''
 
         # validate input
@@ -185,10 +187,11 @@
     @classmethod
     def create_multi_item_summary(cls, outcomes):
         '''Create a string containing a sum of all outcomes, like this:
-        "3 PASSED, 1 INFO, 2 FAILED"
+        ``3 PASSED, 1 INFO, 2 FAILED``
 
-        @param outcomes either one CheckDetail instance or an iterable of
-                        CheckDetails or an iterable of outcome strings
+        :param outcomes: either one :class:`CheckDetail` instance or an iterable
+                          of :class:`CheckDetails <CheckDetail>` or an iterable
+                          of :attr:`outcome` strings
         '''
         # validate input
         if isinstance(outcomes, CheckDetail):
@@ -225,39 +228,39 @@
 
 
 class ReportType(object):
-    ''' Enum for different types of CheckDetail.report_type'''
+    ''' Enum for different types of :attr:`CheckDetail.report_type`'''
     # the values are used as identifiers in a TAP export
-    KOJI_BUILD = 'koji_build'
-    BODHI_UPDATE = 'bodhi_update'
-    YUM_REPOSITORY = 'yum_repository'
+    KOJI_BUILD = 'koji_build'          #:
+    BODHI_UPDATE = 'bodhi_update'      #:
+    YUM_REPOSITORY = 'yum_repository'  #:
 
 
 def export_TAP(*check_details):
     '''Generate TAP output used for reporting to ResultsDB.
 
-    @param *check_details any number of CheckDetail instances
-    @return a string containing TAP output with results for every CheckDetail
-            instance provided
-    @raise TaskotronValueError if CheckDetail.item is empty for any parameter
-                               provided
-
-    Example output:
-    ~~~~~~~~~~~~~~~~~~~~~~~
-    TAP Version 13
-    1..1
-    ok - $CHECKNAME for Koji build xchat-0.5-1.fc20
-      ---
-      details:
-        output: |-
-          xchat.x86_64: W: file-not-utf8 /usr/share/doc/xchat/ChangeLog
-          xchat.x86_64: W: non-conffile-in-etc /etc/gconf/schemas/apps.schemas
-          xchat.x86_64: W: no-manual-page-for-binary xchat
-      item: xchat-0.5-1.fc20
-      outcome: PASSED
-      summary: 5 ERRORS, 10 WARNINGS
-      type: koji_build
-      ...
-    ~~~~~~~~~~~~~~~~~~~~~~~
+    :param check_details: any number of :class:`CheckDetail` instances
+    :return: TAP output with results for every :class:`CheckDetail` instance
+             provided
+    :rtype: str
+    :raise TaskotronValueError: if :attr:`CheckDetail.item` is empty for any
+                                parameter provided
+
+    Example output::
+
+      TAP Version 13
+      1..1
+      ok - $CHECKNAME for Koji build xchat-0.5-1.fc20
+        ---
+        details:
+          output: |-
+            xchat.x86_64: W: file-not-utf8 /usr/share/doc/xchat/ChangeLog
+            xchat.x86_64: W: non-conffile-in-etc /etc/gconf/schemas/apps.schemas
+            xchat.x86_64: W: no-manual-page-for-binary xchat
+        item: xchat-0.5-1.fc20
+        outcome: PASSED
+        summary: 5 ERRORS, 10 WARNINGS
+        type: koji_build
+        ...
     '''
 
     # validate input
@@ -317,9 +320,10 @@
     return '\n'.join(tapout)
 
 def import_TAP(source):
-    '''Parses the source (string in TAP format), and returns list of
-    CheckDetails.
-    @throw TaskotronValueError if TAP syntax is incorrect
+    '''Parses TAP and returns a list of :class:`CheckDetails <CheckDetail>`.
+
+    :param str source: TAP-formatted text
+    :raise TaskotronValueError: if TAP syntax is incorrect
     '''
 
     tap = TAP13()
Index: libtaskotron/config.py
===================================================================
--- libtaskotron/config.py
+++ libtaskotron/config.py
@@ -16,33 +16,33 @@
 from . import file_utils
 
 
-# a list of directories where config files are stored
-# the config files are loaded from these locations in the specified order,
-# and only the first config file found is used (further locations are ignored)
-CONF_DIRS = [
-    # local checkout dir
+CONF_DIRS = [ # local checkout dir first, then system wide dir
     os.path.abspath(os.path.dirname(libtaskotron.__file__) + '/../conf'),
-   '/etc/taskotron',
-    ]
+    '/etc/taskotron']
+'''A list of directories where config files are stored. The config files are
+loaded from these locations in the specified order, and only the first config
+file found is used (further locations are ignored). The first location is
+dynamic, relative to the package location and it reflects the usual config
+location in a git checkout.'''
 
-# the name of our configuration file
 CONF_FILE = 'taskotron.yaml'
+'''the name of our configuration file'''
 
-# environment variable name for setting config profile
 PROFILE_VAR = 'TASKOTRON_PROFILE'
+'''environment variable name for setting config profile'''
 
-# a singleton instance of Config
 _config = None
+'''a singleton instance of Config'''
 
 
 def get_config():
-    '''Get Config instance.
+    '''Get the Config instance. This method is implemented using the singleton
+    pattern - you will always receive the same instance, which will get
+    auto-initialized on the first method call.
 
-    @return either @class Config or its subclass, depending on taskotron
-            profile used. This method is implemented using the singleton pattern
-            - you will always receive the same instance, which will get
-            auto-initialized on the first method call.
-    @throw TaskotronConfigError if file config parsing and handling failed
+    :return: either :class:`.Config` or its subclass, depending on taskotron
+             profile used.
+    :raise TaskotronConfigError: if config file parsing and handling failed
     '''
 
     global _config
@@ -55,8 +55,8 @@
     '''Do everything necessary to fully initialize Config instance, update it
     with external configuration, make all final touches.
 
-    @return Config instance ready to be used
-    @throw TaskotronConfigError if file config parsing and handling failed
+    :return: :class:`.Config` instance ready to be used
+    :raise TaskotronConfigError: if file config parsing and handling failed
     '''
     config = _load()
     log.debug('Using config profile: %s', config.profile)
@@ -70,8 +70,9 @@
 def _load():
     '''Load the configuration, internal (defaults) and external (config files)
 
-    @return Config instance that was updated by external config file contents
-    @throw TaskotronConfigError if file config parsing and handling failed
+    :return: :class:`.Config` instance that was updated by external config file
+             contents
+    :raise TaskotronConfigError: if file config parsing and handling failed
     '''
 
     # first load the defaults and make sure we even want to load config files
@@ -104,8 +105,8 @@
 def _load_defaults(profile):
     '''Load and return Config (or its subclass) based on chosen profile.
 
-    @param profile profile name as defined in config_defaults.ProfileName
-    @return config_defaults.Config instance or its subclass
+    :param str profile: profile name as defined in :class:`.ProfileName`
+    :return: :class:`.Config` instance or its subclass
     '''
 
     if profile == ProfileName.PRODUCTION:
@@ -124,9 +125,11 @@
     '''Find the configuration file in a set of directories. The first match
     is returned.
 
-    @param conf_dirs a list of directories to search through
-    @param conf_file configuration file name
-    @return full path to the configuration file or None if not found
+    :param conf_dirs: a list of directories to search through
+    :type conf_dirs: list of str
+    :param str conf_file: configuration file name
+    :return: full path to the configuration file (string) or ``None`` if not
+             found
     '''
 
     for conf_dir in conf_dirs:
@@ -139,11 +142,11 @@
     '''Parse a configuration file and return it as a dictionary. The option
     values are checked for type correctness against a default Config object.
 
-    @param conf_file file path or file handler to the configuration file in
-                     YAML syntax
-    @return dictionary parsed from the configuration file
-    @throw TaskotronConfigError if any problem occurs during the parsing or some
-                                values have incorrect variable type
+    :param conf_file: file path (string) or file handler to the configuration
+                      file in YAML syntax
+    :return: dictionary parsed from the configuration file
+    :raise TaskotronConfigError: if any problem occurs during the parsing or
+                                 some values have incorrect variable type
     '''
 
     # convert file path to file handle if needed
@@ -200,14 +203,15 @@
 def _merge_config(config, file_config):
     '''Merge a Config object and a dictionary parsed from YAML file.
 
-    @param config a Config instance to merge into
-    @param file_config a dictionary parsed from YAML configuration file to merge
-                       from
-    @return the same Config instance, but with some attributes overwritten with
-            values from file_config
-    @throw AttributeError if file_config contains values that config doesn't
-                          have. But this shouldn't happen, because @function
-                          load_file() should already have checked for that.
+    :param config: a :class:`.Config` instance to merge into
+    :param dict file_config: a dictionary parsed from YAML configuration file to
+                             merge from
+    :return: the same ``config`` instance, but with some attributes overwritten
+             with values from ``file_config``
+    :raise AttributeError: if ``file_config`` contains values that ``config``
+                           doesn't have. But this shouldn't happen, because
+                           :func:`_load_file` should already have checked for
+                           that.
     '''
 
     for option, value in file_config.items():
@@ -222,9 +226,9 @@
 
 def _create_dirs(config):
     '''Create directories in the local file system for appropriate config
-    options - e.g. tmpdir, logdir, etc.
+    options - e.g. ``tmpdir``, ``logdir``, etc.
 
-    @param config Config instance
+    :param config: :class:`.Config` instance
     '''
 
     for dir_ in (config.tmpdir, config.logdir):
Index: libtaskotron/config_defaults.py
===================================================================
--- libtaskotron/config_defaults.py
+++ libtaskotron/config_defaults.py
@@ -13,68 +13,70 @@
     '''Enum of available profile names. These can be specified in the config
     file or as the environment variable.'''
 
-    DEVELOPMENT = 'development'
-    PRODUCTION = 'production'
-    TESTING = 'testing'
+    DEVELOPMENT = 'development'  #:
+    PRODUCTION = 'production'    #:
+    TESTING = 'testing'          #:
 
 
 class Config(object):
     '''Global configuration for Taskotron (development profile).
 
        The documentation for individual options is available in the config
        files (unless they're not present in the config files, then they're
-       documented in the code).
+       documented here).
 
        Implementation notes:
+
        * If you want to add a new option, put it here and optionally into the
          config file as well.
        * If you modify a default value for some option, don't forget to modify
          it in both places - here and in the config file (if present).
-       * Don't assign None as a default value. We need to know a value type in
-         order to check for correct type of user-provided values.
+       * Don't assign ``None`` as a default value. We need to know a value type
+         in order to check for correct type of user-provided values.
     '''
 
-    profile = ProfileName.DEVELOPMENT
+    profile = ProfileName.DEVELOPMENT                                       #:
 
-    reporting_enabled = False
-    report_to_bodhi = True
-    report_to_resultsdb = True
+    reporting_enabled = False                                               #:
+    report_to_bodhi = True                                                  #:
+    report_to_resultsdb = True                                              #:
 
-    koji_url = 'http://koji.fedoraproject.org/kojihub'
-    pkg_url = 'http://kojipkgs.fedoraproject.org/packages'
-    bodhi_server = 'https://admin.fedoraproject.org/updates/'
+    koji_url = 'http://koji.fedoraproject.org/kojihub'                      #:
+    pkg_url = 'http://kojipkgs.fedoraproject.org/packages'                  #:
+    bodhi_server = 'https://admin.fedoraproject.org/updates/'               #:
     resultsdb_server = \
-        'http://resultsdb.qa.fedoraproject.org/resultsdb/api/v1.0/'
-    taskotron_master = 'http://taskotron.qa.fedoraproject.org/taskmaster/'
-    buildbot_task_step = 'runtask'
+        'http://resultsdb.qa.fedoraproject.org/resultsdb/api/v1.0/'         #:
+    taskotron_master = 'http://taskotron.qa.fedoraproject.org/taskmaster/'  #:
+    buildbot_task_step = 'runtask'                                          #:
 
-    bodhi_email_failed_span = 4320
-    bodhi_posting_comments_span = 4320
+    bodhi_posting_comments_span = 4320                                      #:
+                                  # 3 days (3*24*60 = 4320)
 
-    tmpdir = '/var/tmp/taskotron'
-    logdir = '/var/log/taskotron'
+    tmpdir = '/var/tmp/taskotron'                                           #:
+    logdir = '/var/log/taskotron'                                           #:
     main_log_name = 'taskotron.log'
+    '''name of the main log file in :attr:`logdir`'''
 
-    fas_username = 'taskotron'
-    fas_password = ''
+    fas_username = 'taskotron'                                              #:
+    fas_password = ''                                                       #:
 
 
     def __str__(self):
-        # Make this object more readable when printing
+        ''' Make this object more readable when printing '''
         return '<%s: %s>' % (self.__class__.__name__,
                              pprint.pformat(vars(self)))
 
 
 class ProductionConfig(Config):
-    '''Configuration for production profile. Inherits values from @class Config
-    and overrides some. Read @class Config documentation.'''
+    '''Configuration for production profile. Inherits values from
+    :class:`Config` and overrides some. Read Config documentation.'''
 
-    profile = ProfileName.PRODUCTION
-    reports_enabled = True
+    profile = ProfileName.PRODUCTION                                        #:
+    reports_enabled = True                                                  #:
 
 
 class TestingConfig(Config):
-    '''Configuration for testing suite profile. Inherits values from @class
-    Config and overrides some. Read @class Config documentation.'''
+    '''Configuration for testing suite profile. Inherits values from
+    :class:`Config` and overrides some. Read Config documentation.'''
 
-    profile = ProfileName.TESTING
+    profile = ProfileName.TESTING                                           #:
Index: libtaskotron/exceptions.py
===================================================================
--- libtaskotron/exceptions.py
+++ libtaskotron/exceptions.py
@@ -11,7 +11,7 @@
 
 
 class TaskotronValueError(ValueError, TaskotronError):
-    '''Taskotron-specific ValueError'''
+    '''Taskotron-specific :class:`ValueError`'''
     pass
 
 
Index: libtaskotron/file_utils.py
===================================================================
--- libtaskotron/file_utils.py
+++ libtaskotron/file_utils.py
@@ -7,9 +7,10 @@
 
 
 def makedirs(fullpath):
-    '''This is the same as @method os.makedirs(), but does not throw an
-    exception when the destination directory already exists.
-    @raise OSError if directory can't be created
+    '''This is the same as :meth:`os.makedirs`, but does not raise an exception
+    when the destination directory already exists.
+
+    :raise OSError: if directory doesn't exist and can't be created
     '''
     try:
         os.makedirs(fullpath)
@@ -26,10 +27,12 @@
             raise
 
 def get_urlgrabber(**kwargs):
-    '''Get a new instance of URLGrabber that is reasonably set (progress
-       bar when in terminal, automatic retries, connection timeout, etc)
-       @kwargs additional kwargs to add to the URLGrabber constructor
-       @return URLGrabber instance
+    '''Get a new instance of :class:`URLGrabber` that is reasonably configured
+       (progress bar when in terminal, automatic retries, connection timeout,
+       etc)
+
+       :param kwargs: additional kwargs to add to the URLGrabber constructor
+       :return: :class:`urlgrabber.grabber.URLGrabber` instance
     '''
     grabber_args = {'retry': 3, 'timeout': 120}
     grabber_args.update(kwargs)
@@ -50,18 +53,21 @@
 def download(url, dest, overwrite=False, grabber=None):
     '''Download a file.
 
-    @param url file URL to download
-    @param dest either an existing directory (string) or a file path (string).
-                In the latter case all parent directories will be created if
-                necessary and the last fraction of the path will be used as the
-                file name.
-    @param overwrite if the destination file already exists, whether to
-                    overwrite or not. If False, a simple check is performed
-                    whether the remote file is the same as the local file and
-                    re-downloads it anyway if they are not the same.
-    @param grabber custom urlgrabber.grabber.URLGrabber object
-    @return file path of the downloaded file (string)
-    @raise TaskotronRemoteError if download fails or directory can't be created
+    :param str url: file URL to download
+    :param str dest: either an existing directory or a file path. In the latter
+                     case all parent directories will be created if necessary
+                     and the last fraction of the path will be used as the
+                     file name.
+    :param bool overwrite: if the destination file already exists, whether to
+                           overwrite or not. If ``False``, a simple check is
+                           performed whether the remote file is the same as the
+                           local file and re-downloads it anyway if they are not
+                           the same.
+    :param grabber: custom :class:`urlgrabber.grabber.URLGrabber` instance to be
+                    used for actual downloading
+    :return: file path of the downloaded file
+    :rtype: str
+    :raise TaskotronRemoteError: if download fails or directory can't be created
     '''
 
     if not grabber:
Index: libtaskotron/koji_utils.py
===================================================================
--- libtaskotron/koji_utils.py
+++ libtaskotron/koji_utils.py
@@ -18,25 +18,26 @@
 class KojiClient(object):
     '''Helper Koji methods.
 
-    Note: You can access the standard Koji client (from @module koji) as
-    self.session.
+    :ivar koji.ClientSession session: Koji client session
     '''
     def __init__(self, koji_session=None):
         '''Create a new KojiClient
-        @param koji_session an existing Koji session (koji.ClientSession),
-                            or None if you want a new default session to be
-                            created
+
+        :param koji_session: an existing Koji session instance or ``None`` if
+                             you want a new default session to be created
+        :type koji_session: :class:`koji.ClientSession`
         '''
         self.session = (koji_session or
             koji.ClientSession(config.get_config().koji_url))
 
 
     def nvr_to_urls(self, nvr, arches=None, debuginfo=False, src=True):
         '''Get list of URLs for RPMs corresponding to a build.
-        @param nvr build NVR
-        @param arches restrict the arches of builds to provide URLs for. If
-                        basearch i386 is in the list, i686 arch is automatically
-                        added (since that's the arch Koji API uses).
+
+        :param str nvr: build NVR
+        :param arches: restrict the arches of builds to provide URLs for. Note:
+                       If basearch ``i386`` is in the list, ``i686`` arch is
+                       automatically added (since that's the arch Koji API uses).
         '''
         log.info('Querying Koji for a list of RPMS for: %s', nvr)
 
@@ -70,10 +71,11 @@
         '''Retrieve the RPMs associated with a build NVR into the specified
             directory.
 
-        @param nvr build NVR
-        @param rpm_dir location where to store the RPMs
-        @return list of local filenames of the grabbed RPMs
-        @raise TaskotronRemoteError if downloading failed
+        :param str nvr build: NVR
+        :param str rpm_dir: location where to store the RPMs
+        :return: list of local filenames of the grabbed RPMs
+        :rtype: list of str
+        :raise TaskotronRemoteError: if downloading failed
         '''
         rpm_urls = self.nvr_to_urls(nvr,
                                     arches=arches,
@@ -91,9 +93,11 @@
 
     def get_tagged_rpms(self, tag, dest, arches):
         '''Downloads all RPMs of all NVRs tagged by a specific Koji tag.
-        @dest destination directory
-        @arches list of architectures
-        @raise TaskotronRemoteError if downloading failed
+
+        :param str dest: destination directory
+        :param arches: list of architectures
+        :type arches: list of str
+        :raise TaskotronRemoteError: if downloading failed
         '''
         tag_data = self.session.listTagged(tag)
         nvrs = ["%(nvr)s" % x for x in tag_data]
@@ -107,10 +111,10 @@
 def getNEVR(build):
     '''Extract RPM version identifier in NEVR format from Koji build object
 
-    @param build Koji buildinfo dictionary (as returned from @module koji
-                 methods like getBuild())
-    @return NEVR string; epoch is included when non-zero, otherwise omitted
-    @raise TaskotronValueError if @param build is of incorrect type
+    :param dict build: Koji buildinfo dictionary (as returned e.g. from
+                       :meth:`koji.getBuild`)
+    :return: NEVR string; epoch is included when non-zero, otherwise omitted
+    :raise TaskotronValueError: if ``build`` is of incorrect type
     '''
     # validate input
     if (not isinstance(build, collections.Mapping) or 'nvr' not in build or
Index: libtaskotron/logger.py
===================================================================
--- libtaskotron/logger.py
+++ libtaskotron/logger.py
@@ -37,11 +37,13 @@
 def init(name='libtaskotron', level=logging.INFO, stream=True, syslog=False,
          filelog=None):
     """Setup a logger
-    @param name name of the logger, 'libtaskotron' is default
-    @param level level of logging
-    @param stream enables logging to stderr
-    @param syslog enables logging to syslog
-    @param filelog enables logging to a file, the value is the file name
+
+    :param str name: name of the logger, 'libtaskotron' is default
+    :param level: level of logging
+    :type level: level definitions from :mod:`logging`
+    :param bool stream: enables logging to stderr
+    :param bool syslog: enables logging to syslog
+    :param str filelog: enables logging to a file, the value is the file name
     """
 
     # overwrite default except hook
Index: libtaskotron/python_utils.py
===================================================================
--- libtaskotron/python_utils.py
+++ libtaskotron/python_utils.py
@@ -7,11 +7,15 @@
 import collections
 
 
-def listlike(obj):
-    '''Return whether the object is iterable (list, tuple, set, etc), but not
-    string (which is iterable, but we don't often want to consider it as such).
-    This method is useful e.g. if you want to check that a variable contains
-    a list/set/etc of strings.
+def listlike(value):
+    '''Return whether ``value`` is iterable (``list``, ``tuple``, ``set``,
+    etc), but not ``str`` (which is iterable, but we don't often want to
+    consider it as such). This method is useful e.g. if you want to check that a
+    variable contains a list/set/etc of strings.
+
+    :param any value: any object or literal you want to check
+    :return: whether ``value`` is iterable but not string
+    :rtype: bool
     '''
-    return isinstance(obj, collections.Iterable) and not isinstance(obj,
-                                                                    basestring)
+    return (isinstance(value, collections.Iterable) and
+            not isinstance(value, basestring))
Index: libtaskotron/rpm_utils.py
===================================================================
--- libtaskotron/rpm_utils.py
+++ libtaskotron/rpm_utils.py
@@ -13,11 +13,12 @@
 def basearch(arch=None):
     '''
     This returns the 'base' architecture identifier for a specified architecture
-    (e.g. i386 for i[3-6]86), to be used by YUM etc.
+    (e.g. ``i386`` for ``i[3-6]86``), to be used by YUM etc.
 
-    @param arch an arch (string) to be converted to a basearch. If None, then
-           the arch of the current machine is used.
-    @return basearch, or @param arch if no basearch was found for it
+    :param str arch: an architecture to be converted to a basearch. If ``None``,
+                     then the arch of the current machine is used.
+    :return: basearch, or ``arch`` if no basearch was found for it
+    :rtype: str
     '''
     if arch == None:
         arch = os.uname()[4]
@@ -32,20 +33,23 @@
     String identifiers: N - name, E - epoch, V - version, R - release, A -
     architecture.
 
-    @param rpmstr string to be manipulated in a format of N(E)VR (foo-1.2-3.fc20
-                  or bar-4:1.2-3.fc20) or N(E)VRA (foo-1.2-3.fc20.x86_64 or
-                  bar-4:1.2-3.fc20.i686)
-    @param fmt desired format of the string to be returned. Allowed options are:
-               nvr, nevr, nvra, nevra, n, e, v, r, a. If arch is not present in
-               rpmstr but requested in fmt, 'noarch' is used. Epoch is provided
-               only when specifically requested (e.g. fmt='nevr') *and* being
-               non-zero; otherwise it's supressed (the only exception is
-               fmt='e', where you receive 0 for zero epoch).
-    @param end_arch set this to True if rpmstr ends with an architecture
-                    identifier (foo-1.2-3.fc20.x86_64). It's not possible to
-                    reliably distinguish that case automatically.
-    @return string based on the specified format, or integer if fmt='e'
-    @raise TaskotronValueError in @param fmt value is not supported
+    :param str rpmstr: string to be manipulated in a format of N(E)VR
+                       (``foo-1.2-3.fc20`` or ``bar-4:1.2-3.fc20``) or N(E)VRA
+                       (``foo-1.2-3.fc20.x86_64`` or ``bar-4:1.2-3.fc20.i686``)
+    :param str fmt: desired format of the string to be returned. Allowed options
+                    are: ``nvr``, ``nevr``, ``nvra``, ``nevra``, ``n``, ``e``,
+                    ``v``, ``r``, ``a``. If arch is not present in ``rpmstr``
+                    but requested in ``fmt``, ``noarch`` is used. Epoch is
+                    provided only when specifically requested (e.g.
+                    ``fmt='nevr'``) **and** being non-zero; otherwise it's
+                    supressed (the only exception is ``fmt='e'``, where you
+                    receive ``0`` for zero epoch).
+    :param bool end_arch: set this to ``True`` if ``rpmstr`` ends with an
+                          architecture identifier (``foo-1.2-3.fc20.x86_64``).
+                          It's not possible to reliably distinguish that case
+                          automatically.
+    :return: string based on the specified format, or integer if ``fmt='e'``
+    :raise TaskotronValueError: if ``fmt`` value is not supported
     '''
     fmt = fmt.lower()
     supported_formats = ['nvr', 'nevr', 'nvra', 'nevra',
@@ -87,10 +91,13 @@
 def cmpNEVR(nevr1, nevr2):
     '''Compare two RPM version identifiers in NEVR format.
 
-    @param nevr1 a string in N(E)VR format
-    @param nevr2 a string in N(E)VR format
-    @return -1/0/1 if nevr1 < nevr2 / nevr1 == nevr2 / nevr1 > nevr2
-    @raise TaskotronValueError if name in nevr1 doesn't match name in nevr2
+    :param str nevr1: RPM identifier in N(E)VR format
+    :param str nevr2: RPM identifier in N(E)VR format
+    :return: ``-1``/``0``/``1`` if ``nevr1 < nevr2`` / ``nevr1 == nevr2`` /
+             ``nevr1 > nevr2``
+    :rtype: int
+    :raise TaskotronValueError: if name in ``nevr1`` doesn't match name in
+                                ``nevr2``
     '''
     rpmver1 = hawkey.split_nevra(nevr1 + '.noarch')
     rpmver2 = hawkey.split_nevra(nevr2 + '.noarch')
