From 95a34873da2e97be3ae60bb13046a73db122334f Mon Sep 17 00:00:00 2001
From: sidpremkumar <sid.premkumar@gmail.com>
Date: Jul 19 2019 18:38:50 +0000
Subject: Add ReadTheDocs documentation


    * Ensured that all function docstings were formatted correct
    * Added docs folder to hold relevent doc informations
    * Plan to deploy through ReadTheDocs

---

diff --git a/.gitignore b/.gitignore
index 65e5b0f..53c38b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ rpmbuild-output
 venv/*
 venv3/*
 xunit-tests.xml
+docs/build/*
diff --git a/docs/docs-requirements.txt b/docs/docs-requirements.txt
new file mode 100644
index 0000000..52b04f2
--- /dev/null
+++ b/docs/docs-requirements.txt
@@ -0,0 +1 @@
+sphinx_rtd_theme
\ No newline at end of file
diff --git a/docs/source/adding-new-repo-guide.rst b/docs/source/adding-new-repo-guide.rst
new file mode 100644
index 0000000..4998af0
--- /dev/null
+++ b/docs/source/adding-new-repo-guide.rst
@@ -0,0 +1,25 @@
+Adding New Repos Guide 
+=======================
+
+Have you ever wanted to add new upstream repos? Well now you can! 
+
+1. First ensure that your upstream repo is on the Fed Message Bus
+2. Now add two new functions to `upstream.py`
+    * :code:`def hande_REPO-NAME_message(msg, config)`
+        * This function will take in a fedmessage message (msg) and the config dict
+        * This function will return a sync2jira.Intermediary.Issue object
+        * This function will be used when listening to the message bus
+    * :code:`def REPO-NAME_issues(upstream, config)`
+        * This function will take in an upstream repo name and the config dict 
+        * This function will return a generator of sync2jira.Intermediary.Issue objects that contain all upstream Issues
+        * This function will be used to initialize and sync upstream/downstream issues
+3. Now modify the `main.py` functions: 
+    * :code:`def initialize(config)`
+        * Add another section (like Pagure and GitHub) to utlize the :code:`REPO-NAME_issues` function you just made. 
+    * :code:`def listen(config)`
+        * Add another section to the if statement under Pagure and GitHub
+            * :code:`elif 'REPO-NAME' in suffix:`
+            * Now utilize the :code:`handle_REPO-NAME_message` function you just made
+4. If all goes smoothly, your new repo should work with Sync2Jira!
+
+.. note:: If you want to submit a Pull Request, ensure that you add appropriate Unit Tests, Tox is passing, and you have appropriate documentation!
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..9db07fb
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = u'Sync2Jira'
+copyright = u'2019, Ralph Bean'
+author = u'Ralph Bean'
+
+# The short X.Y version
+version = u''
+# The full version, including alpha/beta/rc tags
+release = u'2.0'
+
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.doctest',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.todo',
+    'sphinx.ext.coverage',
+    'sphinx.ext.mathjax',
+    'sphinx.ext.ifconfig',
+    'sphinx.ext.viewcode',
+    'sphinx.ext.githubpages',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['ntemplates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = None
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['nstatic']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself.  Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+html_sidebars = { '**': ['globaltoc.html', 'searchbox.html'] }
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Sync2Jiradoc'
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+    # The paper size ('letterpaper' or 'a4paper').
+    #
+    # 'papersize': 'letterpaper',
+
+    # The font size ('10pt', '11pt' or '12pt').
+    #
+    # 'pointsize': '10pt',
+
+    # Additional stuff for the LaTeX preamble.
+    #
+    # 'preamble': '',
+
+    # Latex figure (float) alignment
+    #
+    # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+    (master_doc, 'Sync2Jira.tex', u'Sync2Jira Documentation',
+     u'Ralph Bean', 'manual'),
+]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    (master_doc, 'sync2jira', u'Sync2Jira Documentation',
+     [author], 1)
+]
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    (master_doc, 'Sync2Jira', u'Sync2Jira Documentation',
+     author, 'Sync2Jira', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+
+# -- Extension configuration -------------------------------------------------
\ No newline at end of file
diff --git a/docs/source/config-file.rst b/docs/source/config-file.rst
new file mode 100644
index 0000000..4518daf
--- /dev/null
+++ b/docs/source/config-file.rst
@@ -0,0 +1,108 @@
+Config File 
+===========
+The config file is made up of multiple parts 
+
+.. code-block:: python
+
+    'admins': ['demo_jira_username']
+
+* Admins can be users who manage Sync2Jira. They will be cc'd in any emails regarding duplicate issues found.
+  
+.. code-block:: python
+
+    'initialize': True
+
+* Initilization set to True will ensure that there is an inital sync done when Sync2Jira starts.
+  It is recommended to leave this as True to ensure that all issues are in sync. 
+
+.. code-block:: python
+
+    'testing': True
+
+* Testing is a flag that will determine if any changes are actually made downstream (on JIRA tickets). 
+  Set to false if you are developing and don't want any changes to take effect.
+
+.. code-block:: python
+
+  'github_token': 'YOUR_TOKEN', 
+
+* This is where you can enter your GitHub API token. 
+
+.. code-block:: python
+
+    'default_jira_instance': 'example'
+
+* This is the default JIRA instance to be used if none is provided in the project.
+
+.. code-block:: python
+
+    'jira': {
+        'example': {
+            'options': {
+                'server': 'https://some_jira_server_somewhere.com',
+                'verify': True,
+            },
+            'basic_auth': ('YOU_USERNAME', 'YOUR_PASSWORD'),
+        },
+    },
+
+* Here you can configure multiple JIRA instances if you have projects with differing downstream JIRA instances.
+  Ensure to name them approproialty, in name of the JIRA instance above is `example`.
+
+.. code-block:: python
+
+    'map': {
+            'pagure': {
+                'Demo_project': {'project': 'FACTORY', 'component': 'gitbz',
+                                'updates': [...], 'owner': 'jira_username'},
+                # 'koji': { 'project': 'BREW', 'component': None, },
+            },
+            'github': {
+                'GITHUB_USERNAME/Demo_project': {'project': 'FACTORY', 'component': 'gitbz',
+                                                'updates': [...], 'owner': 'jira_username'},
+            },
+        },
+
+* You can add your projects here. The 'project' field is associated with downstream JIRA projects, and 'component' with downstream components. 
+  You can add the following to the updates array: 
+    
+    * :code:`'comments'` 
+        * Sync comments and comment edits
+    * :code:`{'tags': {'overwrite': True/False}}` 
+        * Sync tags, do/don't overwrite downstream tags
+    * :code:`{'fixVersion'; {'overwrite': True/False}}`
+        * Sync fixVersion (downstream milestone), do/don't overwrite downstream fixVersion
+    * :code:`{'assignee': {'overwrite': True/False}}` 
+        * Sync assignee (for Github only the first assignee will sync) do/don't overwrite downstream assignee
+    * :code:`'description'` 
+        * Sync description
+    * :code:`'title'`
+        * Sync title
+    * :code:`{'transition': True/'CUSTOM_TRANSITION'}` 
+        * Sync status (open/closed), Sync only status/Attempt to transition JIRA ticket to CUSTOM_TRANSITION on upstream closure
+    
+    .. note:: 
+        
+        :Overwrite: Setting this to :code:`True` will ensure that Upstream (GitHub or Pagure) values will overwrite downstream ones (i.e. if its empty upstream it'll be empty downstream)
+        :CUSTOM_TRANSITION: Setting this value will get Sync2Jira to automatially transition downstream tickets once their upstream counterparts get closed. Set this to whatever 'closed' means downstream.
+
+* It is strongly encouraged for teams to use the :code:`owner` field. If configured, owners will be alerted if Sync2Jira finds dupliate downstream issues. 
+  Further the owner will be used as a default in case the program is unable to find a valid assignee. 
+
+.. code-block:: python
+
+    'filters': {
+            'github': {
+                # Only sync multi-type tickets from bodhi.
+                'fedora-infra/bodhi': {'state': 'open', 'milestone': 4, },
+            },
+        }
+
+* You can also add filters per-project. The following can be added to the filter dict: 
+
+    * :code:`status`
+        * Open/Closed 
+    * :code:`tags`
+        * List of tags to look for
+    * :code:`milestone`
+        * Upstream milestone status
\ No newline at end of file
diff --git a/docs/source/downstream.rst b/docs/source/downstream.rst
new file mode 100644
index 0000000..24b9275
--- /dev/null
+++ b/docs/source/downstream.rst
@@ -0,0 +1,8 @@
+Downstream
+============
+
+.. automodule:: sync2jira.downstream
+    :members:
+    :private-members:
+
+
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..7d092e0
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,34 @@
+Sync2Jira documentation
+=====================================
+
+
+Home
+^^^^^^^^^^^^^^^^^^
+
+* :ref:`search`
+
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Setup Guide
+
+   quickstart
+   config-file
+
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Adding New Repos
+
+   adding-new-repo-guide
+
+
+.. toctree::
+   :maxdepth: 5
+   :caption: Code Documentation
+
+   main
+   upstream
+   downstream
+   intermediary
+   mailer
\ No newline at end of file
diff --git a/docs/source/intermediary.rst b/docs/source/intermediary.rst
new file mode 100644
index 0000000..256bb21
--- /dev/null
+++ b/docs/source/intermediary.rst
@@ -0,0 +1,127 @@
+Intermediary
+============
+
+Sync2Jira converts upstream issues into custom Issue objects. You can see the __init__ below: 
+
+.. code-block:: python
+
+        def __init__(self, source, title, url, upstream, comments,
+                 config, tags, fixVersion, priority, content,
+                 reporter, assignee, status, id, downstream=None):
+            self.source = source
+            self._title = title
+            self.url = url
+            self.upstream = upstream
+            self.comments = comments
+            self.tags = tags
+            self.fixVersion = fixVersion
+            self.priority = priority
+            self.content = content
+            self.reporter = reporter
+            self.assignee = assignee
+            self.status = status
+            self.id = str(id)
+            if not downstream:
+                self.downstream = config['sync2jira']['map'][self.source][upstream]
+            else:
+                self.downstream = downstream
+
+The object has two properties: 
+
+.. code-block:: python
+
+    @property
+    def title(self):
+        return u'[%s] %s' % (self.upstream, self._title)
+
+* This will return the title in downstream form (i.e. [UPSTREAM_REPO] TITLE)
+
+.. code-block:: python
+    
+    @property
+    def upstream_title(self):
+        return self._title
+
+* This will return the raw title (i.e. TITLE)
+
+Currently the service is set up create Issue objects from Pagure and Github: 
+
+.. code-block:: python
+
+    @classmethod
+    def from_pagure(cls, upstream, issue, config):
+        base = config['sync2jira'].get('pagure_url', 'https://pagure.io')
+        comments = []
+        for comment in issue['comments']:
+            # Only add comments that are not Metadata updates
+            if '**Metadata Update' in comment['comment']:
+                continue
+            # Else add the comment
+            # Convert the date to datetime
+            comment['date_created'] = datetime.fromtimestamp(float(comment['date_created']))
+            comments.append({
+                'author': comment['user']['name'],
+                'body': comment['comment'],
+                'name': comment['user']['name'],
+                'id': comment['id'],
+                'date_created': comment['date_created'],
+                'changed': None
+            })
+
+        return Issue(
+            source='pagure',
+            title=issue['title'],
+            url=base + '/%s/issue/%i' % (upstream, issue['id']),
+            upstream=upstream,
+            config=config,
+            comments=comments,
+            tags=issue['tags'],
+            fixVersion=[issue['milestone']],
+            priority=issue['priority'],
+            content=issue['content'],
+            reporter=issue['user'],
+            assignee=issue['assignee'],
+            status=issue['status'],
+            id=issue['date_created']
+        )
+    
+.. code-block:: python
+
+    @classmethod
+    def from_github(cls, upstream, issue, config):
+        comments = []
+        for comment in issue['comments']:
+            comments.append({
+                'author': comment['author'],
+                'name': comment['name'],
+                'body': comment['body'],
+                'id': comment['id'],
+                'date_created': comment['date_created'],
+                'changed': None
+            })
+
+        # Reformat the state field
+        if issue['state']:
+            if issue['state'] == 'open':
+                issue['state'] = 'Open'
+            elif issue['state'] == 'closed':
+                issue['state'] = 'Closed'
+
+        # TODO: Priority is broken
+        return Issue(
+            source='github',
+            title=issue['title'],
+            url=issue['html_url'],
+            upstream=upstream,
+            config=config,
+            comments=comments,
+            tags=issue['labels'],
+            fixVersion=[issue['milestone']],
+            priority=None,
+            content=issue['body'],
+            reporter=issue['user'],
+            assignee=issue['assignees'],
+            status=issue['state'],
+            id=issue['id']
+        )
+.. note:: Currently Priority is broken and is a known issue. 
diff --git a/docs/source/mailer.rst b/docs/source/mailer.rst
new file mode 100644
index 0000000..b734b2a
--- /dev/null
+++ b/docs/source/mailer.rst
@@ -0,0 +1,6 @@
+Mailer
+=======
+
+.. automodule:: sync2jira.mailer
+    :members:
+
diff --git a/docs/source/main.rst b/docs/source/main.rst
new file mode 100644
index 0000000..fc2abdf
--- /dev/null
+++ b/docs/source/main.rst
@@ -0,0 +1,5 @@
+Main
+====
+
+.. automodule:: sync2jira.main
+    :members:
diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst
new file mode 100644
index 0000000..7e26900
--- /dev/null
+++ b/docs/source/quickstart.rst
@@ -0,0 +1,71 @@
+Quick Start
+============
+
+Want to quickly get started working with Sync2Jira? Follow these steps: 
+
+1. **First open up** :code:`fedmsg.d/sync2jira.py`
+
+2. Enter your GitHub token which you can get `here <https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line>`_
+    .. code-block:: python
+        
+        'github_token': 'YOUR_TOKEN',
+
+3. Enter relevent JIRA information
+    .. code-block:: python
+        
+        'default_jira_instance': 'example',
+        'jira': {
+            'example': {
+                'options': {
+                    'server': 'https://some_jira_server_somewhere.com',
+                    'verify': True,
+                },
+                'basic_auth': ('YOU_USERNAME', 'YOUR_PASSWORD'),
+            },
+        },
+    
+    .. note:: You might have to set verify to False
+
+4. Add your upstream repos to the `map` section
+    .. code-block:: python
+        
+        'map': {
+            'pagure': {
+                'Demo_project': {'project': 'FACTORY', 'component': 'gitbz',
+                                 'updates': [...]},
+                # 'koji': { 'project': 'BREW', 'component': None, },
+            },
+            'github': {
+                'GITHUB_USERNAME/Demo_project': {'project': 'FACTORY', 'component': 'gitbz',
+                                                 'updates': [...]},
+            },
+        },
+    
+    .. note:: You can learn more about what can go into the updates list `here <config-file.html>`_
+
+5. Finally you can tweak the config files optional settings to your liking
+    .. code-block:: python
+
+        # Admins to be cc'd in duplicate emails
+            'admins': ['demo_jira_username'],
+        # Scrape sources at startup
+            'initialize': True,
+        # Don't actually make changes to JIRA...
+            'testing': True,
+        
+        'filters': {
+                'github': {
+                    # Only sync multi-type tickets from bodhi.
+                    'fedora-infra/bodhi': {'state': 'open', 'milestone': 4, },
+                },
+            }
+6. Now that you're done with the config file you can install sync2jira and run 
+    .. code-block:: shell 
+
+        python setup.py install
+        >> ....
+        >> Finished processing dependencies for sync2jira==1.7
+        sync2jira
+    .. note:: You might have to add `config['validate_signatures'] = False`. 
+              You can find out more under the `main <main.html#main-anchor>`_.
+
diff --git a/docs/source/upstream.rst b/docs/source/upstream.rst
new file mode 100644
index 0000000..2978ef8
--- /dev/null
+++ b/docs/source/upstream.rst
@@ -0,0 +1,5 @@
+Upstream
+============
+
+.. automodule:: sync2jira.upstream
+    :members:
diff --git a/sync2jira/downstream.py b/sync2jira/downstream.py
index cf86dd7..a74a63f 100644
--- a/sync2jira/downstream.py
+++ b/sync2jira/downstream.py
@@ -45,11 +45,11 @@ jira_cache = {}
 
 def _comment_format(comment):
     """
-    Function to format JIRA comments
-    Args:
-        comment (dict): Upstream comment
-    Returns:
-        Response (str): Comments formatted
+    Function to format JIRA comments.
+
+    :param dict comment: Upstream comment
+    :returns: Comments formatted
+    :rtype: String
     """
     pretty_date = comment['date_created'].strftime("%a %b %d")
     return "[%s] Upstream, %s wrote [%s]:\n\n{quote}\n%s\n{quote}" % (
@@ -60,11 +60,11 @@ def _comment_format_legacy(comment):
     """
     Legacy function to format JIRA comments.
     This is still used to match comments so no
-    duplicates are created
-    Args:
-        comment (dict): Upstream comment
-    Returns:
-        Response (str): Comments formatted
+    duplicates are created.
+
+    :param dict comment: Upstream comment
+    :returns: Comments formatted
+    :rtype: String
     """
     return "Upstream, %s wrote:\n\n{quote}\n%s\n{quote}" % (
         comment['name'], comment['body'])
@@ -72,12 +72,12 @@ def _comment_format_legacy(comment):
 
 def _get_jira_client(issue, config):
     """
-    Function to match and create JIRA client
-    Args:
-        issue (sync2jira.intermediary.Issue): Issue object
-        config (dict): Config dict
-    Returns:
-        client (jira.client.JIRA): Matching JIRA client
+    Function to match and create JIRA client.
+
+    :param sync2jira.intermediary.Issue issue: Issue object
+    :param dict config: Config dict
+    :returns: Matching JIRA client
+    :rtype: jira.client.JIRA
     """
     # The name of the jira instance to use is stored under the 'map'
     # key in the config where each upstream is mapped to jira projects.
@@ -104,14 +104,14 @@ def _get_jira_client(issue, config):
 
 def _matching_jira_issue_query(client, issue, config, free=False):
     """
-    API calls that find matching JIRA tickets if any are present
-    Args:
-        client (jira.client.JIRA): JIRA client
-        issue (sync2jira.intermediary.Issue): Issue object
-        config (dict): Config dict
-        free (Bool): Free tag to add 'statusCategory != Done' to query
-    Returns:
-        results (lst): Returns a list of matching JIRA issues if any are found
+    API calls that find matching JIRA tickets if any are present.
+
+    :param jira.client.JIRA client: JIRA client
+    :param sync2jira.intermediary.Issue issue: Issue object
+    :param Dict config: Config dict
+    :param Bool free: Free tag to add 'statusCategory != Done' to query
+    :returns: results: Returns a list of matching JIRA issues if any are found
+    :rtype: List
     """
     # Searches for any remote link to the issue.url
     query = 'issueFunction in linkedIssuesOfRemote("%s") and ' \
@@ -177,15 +177,14 @@ def _matching_jira_issue_query(client, issue, config, free=False):
 def alert_user_of_duplicate_issues(issue, final_result, results_of_query,
                                    config, client):
     """
-    Alerts owner of duplicate downstream issues
-    Args:
-        issue (sync2jira.intermediate.Issue): Upstream Issue object
-        final_result (list): Issue selected by matching algorithm
-        results_of_query (list): Result of JQL query
-        config (dict): Config dict
-        client (jira.client.JIRA): JIRA client
-    Returns:
-        Nothing
+    Alerts owner of duplicate downstream issues.
+
+    :param sync2jira.intermediate.Issue issue: Upstream Issue object
+    :param List final_result: Issue selected by matching algorithm
+    :param List results_of_query: Result of JQL query
+    :param Dict config: Config dict
+    :param jira.client.JIRA client: JIRA client
+    :returns: Nothing
     """
     # First remove final_result from results_of_query
     results_of_query.remove(final_result[0])
@@ -273,12 +272,12 @@ def alert_user_of_duplicate_issues(issue, final_result, results_of_query,
 
 def find_username(issue, config):
     """
-    Finds JIRA username for an issue object
-    Args:
-        issue (sync2jira.intermediary.Issue): Issue object
-        config (dict): Config dict
-    Returns:
-        return (str): Username string
+    Finds JIRA username for an issue object.
+
+    :param sync2jira.intermediary.Issue issue: Issue object
+    :param Dict config: Config dict
+    :returns: Username string
+    :rtype: String
     """
     jira_instance = issue.downstream.get('jira_instance', False)
     if not jira_instance:
@@ -292,16 +291,14 @@ def find_username(issue, config):
 def check_comments_for_duplicate(client, result, username):
     """
     Checks comment of JIRA issue to see if it has been
-    marked as a duplicate
-    Args:
-        client (jira.client.JIRA): JIRA client)
-        result (jira.resource.Issue): JIRA issue
-        username (str): Username of JIRA user
-    Returns:
-        return (bool): True if duplicate comment was not found
-        *Or*
-        return (jira.resource.Issue): JIRA issue if we were able to
-                                      find it
+    marked as a duplicate.
+
+    :param jira.client.JIRA client: JIRA client)
+    :param jira.resource.Issue result: JIRA issue
+    :param string username: Username of JIRA user
+    :returns: True if duplicate comment was not found or JIRA issue if \
+              we were able to find it
+    :rtype: Bool or jira.resource.Issue
     """
     for comment in client.comments(result):
         search = re.search(r'Marking as duplicate of (\w*)-(\d*)',
@@ -314,12 +311,12 @@ def check_comments_for_duplicate(client, result, username):
 
 def _find_comment_in_jira(comment, j_comments):
     """
-    Helper function to filter out comments that are matching
-    Args:
-        comment (dict): Individual comment from upstream
-        j_comments (lst(jira.resources.Comment)): Comments from JIRA downstream
-    Returns:
-        Item/None (jira.resource.Comment)
+    Helper function to filter out comments that are matching.
+
+    :param Dict comment: Individual comment from upstream
+    :param List j_comments: Comments from JIRA downstream
+    :returns: Item/None
+    :rtype: jira.resource.Comment/None
     """
     formatted_comment = _comment_format(comment)
     legacy_formatted_comment = _comment_format_legacy(comment)
@@ -349,12 +346,12 @@ def _find_comment_in_jira(comment, j_comments):
 
 def _comment_matching(g_comments, j_comments):
     """
-    Function to filter out comments that are matching
-    Args:
-        g_comments (lst(dict)): Comments from Issue object
-        j_comments (lst(jira.resources.Comment)): Comments from JIRA downstream
-    Returns:
-        response (lst): Returns a list of comments that are not matching
+    Function to filter out comments that are matching.
+
+    :param List g_comments: Comments from Issue object
+    :param List j_comments: Comments from JIRA downstream
+    :returns: Returns a list of comments that are not matching
+    :rtype: List
     """
     return list(
         filter(
@@ -366,14 +363,14 @@ def _comment_matching(g_comments, j_comments):
 
 def _get_existing_jira_issue(client, issue, config):
     """
-    Get a jira issue by the linked remote issue.
+    Get a jira issue by the linked remote issue. \
     This is the new supported way of doing this.
-    Args:
-        client (jira.client.JIRA): JIRA client
-        issue (sync2jira.intermediary.Issue): Issue object
-        config (dict): Config dict
-    Returns:
-        response (lst): Returns a list of matching JIRA issues if any are found
+
+    :param jira.client.JIRA client: JIRA client
+    :param sync2jira.intermediary.Issue issue: Issue object
+    :param Dict config: Config dict
+    :returns: Returns a list of matching JIRA issues if any are found
+    :rtype: List
     """
     results = _matching_jira_issue_query(client, issue, config)
     if results:
@@ -383,9 +380,10 @@ def _get_existing_jira_issue(client, issue, config):
 
 
 def _get_existing_jira_issue_legacy(client, issue, config):
-    """ This is our old way of matching issues: use the special url field.
-
+    """
+    This is our old way of matching issues: use the special url field.
     This will be phased out and removed in a future release.
+
     """
 
     kwargs = dict(issue.downstream.items())
@@ -405,17 +403,13 @@ def _get_existing_jira_issue_legacy(client, issue, config):
 
 def _attach_link(client, downstream, remote_link):
     """
-    Attaches the upstream link to the JIRA ticket
-    Args:
-        client (jira.client.JIRA): JIRA client
-        downstream (jira.resources.Issue): Response from
-                                           creating the
-                                           JIRA ticket
-        remote_link():
-    Returns:
-        downstream (jira.resources.Issue): Response from
-                                           creating the
-                                           JIRA ticket
+    Attaches the upstream link to the JIRA ticket.
+
+    :param jira.client.JIRA client: JIRA client
+    :param jira.resources.Issue downstream: Response from creating the JIRA ticket
+    :param str remote_link: Remote link
+    :return: downstream: Response from creating the JIRA ticket
+    :rtype: jira.resources.Issue
     """
     log.info("   Attaching tracking link %r to %r", remote_link, downstream.key)
     modified_desc = downstream.fields.description + " "
@@ -438,9 +432,9 @@ def _attach_link(client, downstream, remote_link):
 
 
 def _upgrade_jira_issue(client, downstream, issue, config):
-    """ Given an old legacy-style downstream issue...
+    """
+    Given an old legacy-style downstream issue...
     ...upgrade it to a new-style issue.
-
     Simply mark it with an external-url field value.
     """
     log.info("    Upgrading %r %r issue for %r", downstream.key, issue.downstream, issue)
@@ -456,15 +450,13 @@ def _upgrade_jira_issue(client, downstream, issue, config):
 def assign_user(client, issue, downstream, remove_all=False):
     """
     Attempts to assigns a JIRA issue to the correct
-    user based on the issue
-    Args:
-        client (jira.client.JIRA): JIRA Client
-        issue (sync2jira.intermediary.Issue): Issue object
-        downstream (jira.resources.Issue): JIRA issue object
-        remove_all (bool): Flag to indicate if we should reset
-                          the assignees in the JIRA issue
-    Returns:
-        Nothing
+    user based on the issue.
+
+    :param jira.client.JIRA client: JIRA Client
+    :param sync2jira.intermediary.Issue issue: Issue object
+    :param jira.resources.Issue downstream: JIRA issue object
+    :param Bool remove_all: Flag to indicate if we should reset the assignees in the JIRA issue
+    :returns: Nothing
     """
     # If removeAll flag, then we need to reset the assignees
     if remove_all:
@@ -500,14 +492,13 @@ def assign_user(client, issue, downstream, remove_all=False):
 def _create_jira_issue(client, issue, config):
     """
     Create a JIRA issue and adds all relevant
-    information in the issue to the JIRA issue
-    Args:
-        client (jira.client.JIRA): JIRA client
-        issue (sync2jira.intermediary.Issue): Issue object
-        config (dict): Config dict
-    Returns:
-        downstream (jira.resources.Issue): Returns JIRA issue
-                                           that was created
+    information in the issue to the JIRA issue.
+
+    :param jira.client.JIRA client: JIRA client
+    :param sync2jira.intermediary.Issue issue: Issue object
+    :param Dict config: Config dict
+    :returns: Returns JIRA issue that was created
+    :rtype: jira.resources.Issue
     """
     log.info("   Creating %r issue for %r", issue.downstream, issue)
     if config['sync2jira']['testing']:
@@ -560,12 +551,12 @@ def _create_jira_issue(client, issue, config):
 def _label_matching(jira_labels, issue_labels):
     """
     Filters through jira_labels to ensure no duplicate labels are present and
-    no jira_labels are removed
-    Args:
-        jira_labels (lst(str)): Existing JIRA labels
-        issue_labels (lst(str): Upstream labels
-    Returns:
-        updated_labels (lst(str)): Updated filtered labels
+    no jira_labels are removed.
+
+    :param List jira_labels: Existing JIRA labels
+    :param List issue_labels: Upstream labels
+    :returns: Updated filtered labels
+    :rtype: List
     """
     # We want to get the union of the jira_labels and the issue_labels
     # i.e. all the labels in jira_labels and no duplicates from issue_labels
@@ -576,13 +567,12 @@ def _label_matching(jira_labels, issue_labels):
 
 def _update_jira_issue(existing, issue, client):
     """
-    Updates an existing JIRA issue (i.e. tags, assignee, comments etc)
-    Args:
-        existing (jira.resources.Issue): Existing JIRA issue that was found
-        issue (sync2jira.intermediary.Issue): Upstream issue we're pulling data from
-        client (jira.client.JIRA): JIRA Client
-    Returns:
-        Nothing
+    Updates an existing JIRA issue (i.e. tags, assignee, comments etc).
+
+    :param jira.resources.Issue existing: Existing JIRA issue that was found
+    :param sync2jira.intermediary.Issue issue: Upstream issue we're pulling data from
+    :param jira.client.JIRA client: JIRA Client
+    :returns: Nothing
     """
     # Start with comments
     # Only synchronize comments for listings that op-in
@@ -638,13 +628,12 @@ def _update_jira_issue(existing, issue, client):
 
 def _update_transition(client, existing, issue):
     """
-    Helper function to update the transition of a downstream JIRA issue
-    Args:
-        client (jira.client.JIRA): JIRA client
-        existing (jira.resource.Issue): Existing JIRA issue
-        issue (sync2jira.intermediary.Issue): Upstream issue
-    Returns:
-        Nothing
+    Helper function to update the transition of a downstream JIRA issue.
+
+    :param jira.client.JIRA client: JIRA client
+    :param jira.resource.Issue existing: Existing JIRA issue
+    :param sync2jira.intermediary.Issue issue: Upstream issue
+    :returns: Nothing
     """
     # Update the issue status in the JIRA description
     # Format the status
@@ -702,12 +691,11 @@ def _update_transition(client, existing, issue):
 
 def _update_title(issue, existing):
     """
-    Helper function to sync upstream/downstream title
-    Args:
-        issue (sync2jira.intermediary.Issue): Upstream issue
-        existing (jira.resource.Issue): Existing JIRA issue
-    Returns:
-        Nothing
+    Helper function to sync upstream/downstream title.
+
+    :param sync2jira.intermediary.Issue issue: Upstream issue
+    :param jira.resource.Issue existing: Existing JIRA issue
+    :returns: Nothing
     """
     new_description = existing.fields.description
     if not new_description:
@@ -750,13 +738,12 @@ def _update_title(issue, existing):
 
 def _update_comments(client, existing, issue):
     """
-    Helper function to sync comments between existing JIRA issue and upstream issue
-    Args:
-        client (jira.client.JIRA): JIRA client
-        existing (jira.resource.Issue): Existing JIRA issue
-        issue (sync2jira.intermediary.Issue): Upstream issue
-    Returns:
-        Nothing
+    Helper function to sync comments between existing JIRA issue and upstream issue.
+
+    :param jira.client.JIRA client: JIRA client
+    :param jira.resource.Issue existing: Existing JIRA issue
+    :param sync2jira.intermediary.Issue issue: Upstream issue
+    :returns: Nothing
     """
     # First get all existing comments
     comments = client.comments(existing)
@@ -773,13 +760,12 @@ def _update_comments(client, existing, issue):
 
 def _update_fixVersion(updates, existing, issue):
     """
-    Helper function to sync comments between existing JIRA issue and upstream issue
-    Args:
-        updates (list): Downstream updates requested by the user
-        existing (jira.resource.Issue): Existing JIRA issue
-        issue (sync2jira.intermediary.Issue): Upstream issue
-    Returns:
-        Nothing
+    Helper function to sync comments between existing JIRA issue and upstream issue.
+
+    :param List updates: Downstream updates requested by the user
+    :param jira.resource.Issue existing: Existing JIRA issue
+    :param sync2jira.intermediary.Issue issue: Upstream issue
+    :returns: Nothing
     """
     fix_version = []
     # If we are not supposed to overwrite JIRA content
@@ -827,14 +813,13 @@ def _update_fixVersion(updates, existing, issue):
 
 def _update_assignee(client, existing, issue, updates):
     """
-    Helper function update existing JIRA assignee from downstream issue
-    Args:
-        client (jira.client.JIRA): JIRA client
-        existing (jira.resource.Issue): Existing JIRA issue
-        issue (sync2jira.intermediary.Issue): Upstream issue
-        updates (list): Downstream updates requested by the user
-    Returns:
-        Nothing
+        Helper function update existing JIRA assignee from downstream issue.
+
+        :param jira.client.JIRA client: JIRA client
+        :param jira.resource.Issue existing: Existing JIRA issue
+        :param sync2jira.intermediary.Issue issue: Upstream issue
+        :param List updates: Downstream updates requested by the user
+        :returns: Nothing
     """
     # First check if overwrite is set to True
     try:
@@ -876,13 +861,12 @@ def _update_assignee(client, existing, issue, updates):
 
 def _update_tags(updates, existing, issue):
     """
-    Helper function to sync tags between upstream issue and downstream JIRA issue
-    Args:
-        updates (list): Downstream updates requested by the user
-        existing (jira.resource.Issue): Existing JIRA issue
-        issue (sync2jira.intermediary.Issue): Upstream issue
-    Returns:
-        Nothing
+    Helper function to sync tags between upstream issue and downstream JIRA issue.
+
+    :param List updates: Downstream updates requested by the user
+    :param jira.resource.Issue existing: Existing JIRA issue
+    :param sync2jira.intermediary.Issue issue: Upstream issue
+    :returns: Nothing
     """
     # First get all existing tags on the issue
     updated_labels = issue.tags
@@ -908,6 +892,13 @@ def _update_tags(updates, existing, issue):
 
 
 def _update_description(existing, issue):
+    """
+    Helper function to sync description between upstream issue and downstream JIRA issue.
+
+    :param jira.resource.Issue existing: Existing JIRA issue
+    :param sync2jira.intermediary.Issue issue: Upstream issue
+    :returns: Nothing
+    """
     new_description = existing.fields.description
     if not new_description:
         new_description = ''
@@ -973,11 +964,11 @@ def _update_description(existing, issue):
 
 def verify_tags(tags):
     """
-    Helper function to ensure tag are JIRA ready :)
-    Args:
-        tags (lst(str)): Input tags
-    Returns:
-        updated_tags (lst(str)): Updates tags
+    Helper function to ensure tag are JIRA ready :).
+
+    :param List tags: Input tags
+    :returns: Updates tags
+    :rtype: List
     """
     updated_tags = []
     for tag in tags:
@@ -988,12 +979,11 @@ def verify_tags(tags):
 def sync_with_jira(issue, config):
     """
     Attempts to sync a upstream issue with JIRA (i.e. by finding
-    an existing issue or creating a new one)
-    Args:
-        issue (sync2jira.intermediary.Issue): Issue object
-        config (dict): Config dict
-    Returns:
-        Nothing
+    an existing issue or creating a new one).
+
+    :param sync2jira.intermediary.Issue issue: Issue object
+    :param Dict config: Config dict
+    :returns: Nothing
     """
 
     log.info("   Considering upstream %s, %s", issue.url, issue.title)
@@ -1035,6 +1025,15 @@ def sync_with_jira(issue, config):
 
 
 def _close_as_duplicate(client, duplicate, keeper, config):
+    """
+    Helper function to close an issue as a duplicate.
+
+    :param jira.client client: JIRA Client
+    :param jira.resources.Issue duplicate: Duplicate JIRA Issue
+    :param jira.resources.Issue keeper: JIRA issue to keep
+    :param Dict config: Config dict
+    :returns: Nothing
+    """
     log.info("  Closing %s as duplicate of %s", duplicate.permalink(), keeper.permalink())
     if config['sync2jira']['testing']:
         log.info("      Testing flag is true.  Skipping actual delete.")
@@ -1079,6 +1078,13 @@ def _close_as_duplicate(client, duplicate, keeper, config):
 
 
 def close_duplicates(issue, config):
+    """
+    Function to close duplicate JIRA issues.
+
+    :param sync2jira.intermediary.Issue issue: Upstream Issue
+    :param Dict config: Config dict
+    :returns: Nothing
+    """
     # Create a client connection for this issue
     client = _get_jira_client(issue, config)
 
diff --git a/sync2jira/mailer.py b/sync2jira/mailer.py
index edbfed2..99a6627 100644
--- a/sync2jira/mailer.py
+++ b/sync2jira/mailer.py
@@ -13,12 +13,15 @@ DEFAULT_SERVER = os.environ['DEFAULT_SERVER']
 
 
 def send_mail(recipients, subject, text, cc):
-    """ Sends email to recipients
-    :param list recipients : recipients of email
-    :param string subject : subject of the email
-    :param string text: HTML text
-    :param: list cc : cc of the email
-    :pram string text: text of the email
+    """
+    Sends email to recipients.
+
+    :param List recipients: recipients of email
+    :param String subject: subject of the email
+    :param String text: HTML text
+    :param String cc: cc of the email
+    :param String text: text of the email
+    :returns: Nothing
     """
     _cfg = {}
     _cfg.setdefault("server", DEFAULT_SERVER)
diff --git a/sync2jira/main.py b/sync2jira/main.py
index 9c3148a..560145e 100644
--- a/sync2jira/main.py
+++ b/sync2jira/main.py
@@ -66,13 +66,12 @@ handlers = {
 
 def load_config(loader=fedmsg.config.load_config):
     """
-    Generates and validates the config file
-    that will be used by fedmsg and JIRA client
-    Args:
-        loader (function): Function to set up runtime config
-    Returns:
-        config (dict): The config dict to be used
-                        later in the program
+    Generates and validates the config file \
+    that will be used by fedmsg and JIRA client.
+
+    :param Function loader: Function to set up runtime config
+    :returns: The config dict to be used later in the program
+    :rtype: Dict
     """
     config = loader()
 
@@ -110,13 +109,12 @@ def load_config(loader=fedmsg.config.load_config):
 
 def listen(config):
     """
-    Listens to activity on upstream repos on pagure and github
-    via fedmsg, and syncs new issues there to the JIRA instance
+    Listens to activity on upstream repos on pagure and github \
+    via fedmsg, and syncs new issues there to the JIRA instance \
     defined in 'fedmsg.d/sync2jira.py'
-    Args:
-        config (dict): Config dict
-    Returns:
-        Nothing
+
+    :param Dict config: Config dict
+    :returns: Nothing
     """
     if not config['sync2jira'].get('listen'):
         log.info("`listen` is disabled.  Exiting.")
@@ -145,14 +143,13 @@ def listen(config):
 
 def initialize(config):
     """
-    Initial initialization needed to sync any upstream
-    repo with JIRA. Goes through all issues and
-    checks if they're already on JIRA / Need to be
-    created
-    Args:
-        config (dict): Config dict for JIRA
-    Returns:
-        Nothing
+    Initial initialization needed to sync any upstream \
+    repo with JIRA. Goes through all issues and \
+    checks if they're already on JIRA / Need to be \
+    created.
+
+    :param Dict config: Config dict for JIRA
+    :returns: Nothing
     """
     log.info("   Running initialization to sync all issues from upstream to jira")
     log.info("   Testing flag is %r", config['sync2jira']['testing'])
@@ -179,11 +176,9 @@ def initialize(config):
 def main():
     """
     Main function to check for initial sync
-    and listen for fedmgs
-    Args:
-        Nothing
-    Returns:
-        Nothing
+    and listen for fedmgs.
+
+    :return: Nothing
     """
     config = load_config()
     logging.basicConfig(level=logging.INFO)
@@ -199,6 +194,11 @@ def main():
 
 
 def list_managed():
+    """
+    Function to list URL for issues under map in config.
+
+    :return: Nothing
+    """
     config = load_config()
     mapping = config['sync2jira']['map']
     warnings.simplefilter("ignore")
@@ -213,6 +213,11 @@ def list_managed():
 
 
 def close_duplicates():
+    """
+    Function to close duplicate functions. Uses downstream:close_duplicates.
+
+    :return: Nothing
+    """
     config = load_config()
     logging.basicConfig(level=logging.INFO)
     log.info("   Testing flag is %r", config['sync2jira']['testing'])
diff --git a/sync2jira/upstream.py b/sync2jira/upstream.py
index 3484cd6..081ef32 100644
--- a/sync2jira/upstream.py
+++ b/sync2jira/upstream.py
@@ -37,6 +37,14 @@ log = logging.getLogger(__name__)
 
 
 def handle_github_message(msg, config):
+    """
+    Handle GitHub message from FedMsg.
+
+    :param Dict msg: FedMsg Message
+    :param Dict config: Config File
+    :returns: Issue object
+    :rtype: sync2jira.intermediary.Issue
+    """
     owner = msg['msg']['repository']['owner']['login']
     repo = msg['msg']['repository']['name']
     upstream = '{owner}/{repo}'.format(owner=owner, repo=repo)
@@ -125,6 +133,14 @@ def handle_github_message(msg, config):
 
 
 def handle_pagure_message(msg, config):
+    """
+    Handle Pagure message from FedMsg.
+
+    :param Dict msg: FedMsg Message
+    :param Dict config: Config File
+    :returns: Issue object
+    :rtype: sync2jira.intermediary.Issue
+    """
     upstream = msg['msg']['project']['name']
     ns = msg['msg']['project'].get('namespace') or None
     if ns:
@@ -194,6 +210,14 @@ def handle_pagure_message(msg, config):
 
 
 def pagure_issues(upstream, config):
+    """
+    Creates a Generator for all Pagure issues in upstream repo.
+
+    :param String upstream: Upstream Repo
+    :param Dict config: Config Dict
+    :returns: Pagure Issue object generator
+    :rtype: sync2jira.intermediary.Issue
+    """
     base = config['sync2jira'].get('pagure_url', 'https://pagure.io')
     url = base + '/api/0/' + upstream + '/issues'
 
@@ -224,6 +248,14 @@ def pagure_issues(upstream, config):
 
 
 def github_issues(upstream, config):
+    """
+    Creates a Generator for all GitHub issues in upstream repo.
+
+    :param String upstream: Upstream Repo
+    :param Dict config: Config Dict
+    :returns: Pagure Issue object generator
+    :rtype: sync2jira.intermediary.Issue
+    """
     token = config['sync2jira'].get('github_token')
     if not token:
         headers = {}
@@ -321,8 +353,9 @@ def _get_all_github_issues(url, headers):
 
 
 def _github_link_field_to_dict(field):
-    """ Utility for ripping apart github's Link header field.
-    It's kind of ugly.
+    """
+        Utility for ripping apart github's Link header field.
+        It's kind of ugly.
     """
 
     if not field:
@@ -336,6 +369,9 @@ def _github_link_field_to_dict(field):
 
 
 def _fetch_github_data(url, headers):
+    """
+        Helper function to gather GitHub data
+    """
     response = requests.get(url, headers=headers)
     if not bool(response):
         try:

