From 3d6aff5ae5cd06875da2ba80b8f18c7445548b8b Mon Sep 17 00:00:00 2001
From: Benjamin Bertrand <benjamin.bertrand@esss.se>
Date: Sun, 4 Feb 2018 22:18:24 +0100
Subject: [PATCH] Add initial Sphinx documentation

---
 .gitignore           |   1 +
 app/api/inventory.py |  68 +++++++++++++++-
 app/api/network.py   |  44 ++++++++++-
 app/api/user.py      |  24 +++++-
 docs/Makefile        |  20 +++++
 docs/api.rst         |  30 ++++++++
 docs/conf.py         | 179 +++++++++++++++++++++++++++++++++++++++++++
 docs/index.rst       |  28 +++++++
 8 files changed, 385 insertions(+), 9 deletions(-)
 create mode 100644 docs/Makefile
 create mode 100644 docs/api.rst
 create mode 100644 docs/conf.py
 create mode 100644 docs/index.rst

diff --git a/.gitignore b/.gitignore
index 32a48f2..8b50b39 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ settings*.cfg
 /data
 .cache
 .coverage
+/docs/_build
diff --git a/app/api/inventory.py b/app/api/inventory.py
index 8af2af5..653dfb3 100644
--- a/app/api/inventory.py
+++ b/app/api/inventory.py
@@ -35,6 +35,10 @@ def get_item_by_id_or_ics_id(id_):
 @bp.route('/items')
 @jwt_required
 def get_items():
+    """Return items
+
+    .. :quickref: Inventory; Get items
+    """
     return get_generic_model(models.Item,
                              order_by=models.Item.created_at)
 
@@ -42,7 +46,10 @@ def get_items():
 @bp.route('/items/<id_>')
 @jwt_required
 def get_item(id_):
-    """Retrieve item by id or ICS id"""
+    """Retrieve item by id or ICS id
+
+    .. :quickref: Inventory; Get item by id or ICS id
+    """
     item = get_item_by_id_or_ics_id(id_)
     return jsonify(item.to_dict())
 
@@ -51,7 +58,12 @@ def get_item(id_):
 @jwt_required
 @jwt_groups_accepted('admin', 'create')
 def create_item():
-    """Register a new item"""
+    """Register a new item
+
+    .. :quickref: Inventory; Create new item
+
+    :status 201: item created
+    """
     # People should assign an ICS id to a serial number when creating
     # an item so ics_id should also be a mandatory field.
     # But there are existing items (in confluence and JIRA) that we want to
@@ -66,7 +78,10 @@ def create_item():
 def patch_item(id_):
     """Patch an existing item
 
-    id_ can be the primary key or the ics_id field
+    .. :quickref: Inventory; Update existing item
+
+    id\_ can be the primary key or the ics_id field
+
     Fields allowed to update are:
         - ics_id ONLY if current is temporary (422 returned otherwise)
         - manufacturer
@@ -75,7 +90,8 @@ def patch_item(id_):
         - status
         - parent
 
-    422 is returned if other fields are given.
+    :status 200: item updated
+    :status 422: invalid field
     """
     data = request.get_json()
     if data is None:
@@ -115,6 +131,10 @@ def patch_item(id_):
 @bp.route('/items/<id_>/comments')
 @jwt_required
 def get_item_comments(id_):
+    """Get item comments
+
+    .. :quickref: Inventory; Get item comments
+    """
     item = get_item_by_id_or_ics_id(id_)
     return jsonify([comment.to_dict() for comment in item.comments])
 
@@ -123,6 +143,10 @@ def get_item_comments(id_):
 @jwt_required
 @jwt_groups_accepted('admin', 'create')
 def create_item_comment(id_):
+    """Create a comment on item
+
+    .. :quickref: Inventory; Create comment on item
+    """
     item = get_item_by_id_or_ics_id(id_)
     return create_generic_model(models.ItemComment,
                                 mandatory_fields=('body',),
@@ -132,12 +156,20 @@ def create_item_comment(id_):
 @bp.route('/actions')
 @jwt_required
 def get_actions():
+    """Get actions
+
+    .. :quickref: Inventory; Get actions
+    """
     return get_generic_model(models.Action)
 
 
 @bp.route('/manufacturers')
 @jwt_required
 def get_manufacturers():
+    """Get manufacturers
+
+    .. :quickref: Inventory; Get manufacturers
+    """
     return get_generic_model(models.Manufacturer)
 
 
@@ -145,12 +177,20 @@ def get_manufacturers():
 @jwt_required
 @jwt_groups_accepted('admin', 'create')
 def create_manufacturer():
+    """Create a new manufacturer
+
+    .. :quickref: Inventory; Create new manufacturer
+    """
     return create_generic_model(models.Manufacturer)
 
 
 @bp.route('/models')
 @jwt_required
 def get_models():
+    """Get models
+
+    .. :quickref: Inventory; Get models
+    """
     return get_generic_model(models.Model)
 
 
@@ -158,12 +198,20 @@ def get_models():
 @jwt_required
 @jwt_groups_accepted('admin', 'create')
 def create_model():
+    """Create a new model
+
+    .. :quickref: Inventory; Create new model
+    """
     return create_generic_model(models.Model)
 
 
 @bp.route('/locations')
 @jwt_required
 def get_locations():
+    """Get locations
+
+    .. :quickref: Inventory; Get locations
+    """
     return get_generic_model(models.Location)
 
 
@@ -171,12 +219,20 @@ def get_locations():
 @jwt_required
 @jwt_groups_accepted('admin', 'create')
 def create_locations():
+    """Create a new location
+
+    .. :quickref: Inventory; Create new location
+    """
     return create_generic_model(models.Location)
 
 
 @bp.route('/statuses')
 @jwt_required
 def get_status():
+    """Get statuses
+
+    .. :quickref: Inventory; Get statuses
+    """
     return get_generic_model(models.Status)
 
 
@@ -184,4 +240,8 @@ def get_status():
 @jwt_required
 @jwt_groups_accepted('admin', 'create')
 def create_status():
+    """Create a new status
+
+    .. :quickref: Inventory; Create new status
+    """
     return create_generic_model(models.Status)
diff --git a/app/api/network.py b/app/api/network.py
index 10902ad..a834dd3 100644
--- a/app/api/network.py
+++ b/app/api/network.py
@@ -21,6 +21,10 @@ bp = Blueprint('network_api', __name__)
 @bp.route('/scopes')
 @jwt_required
 def get_scopes():
+    """Return network scopes
+
+    .. :quickref: Network; Get network scopes
+    """
     return get_generic_model(models.NetworkScope,
                              order_by=models.NetworkScope.name)
 
@@ -29,7 +33,10 @@ def get_scopes():
 @jwt_required
 @jwt_groups_accepted('admin')
 def create_scope():
-    """Create a new network scope"""
+    """Create a new network scope
+
+    .. :quickref: Network; Create new network scope
+    """
     return create_generic_model(models.NetworkScope, mandatory_fields=(
         'name', 'first_vlan', 'last_vlan', 'supernet'))
 
@@ -37,6 +44,10 @@ def create_scope():
 @bp.route('/networks')
 @jwt_required
 def get_networks():
+    """Return networks
+
+    .. :quickref: Network; Get networks
+    """
     return get_generic_model(models.Network,
                              order_by=models.Network.address)
 
@@ -45,7 +56,10 @@ def get_networks():
 @jwt_required
 @jwt_groups_accepted('admin')
 def create_network():
-    """Create a new network"""
+    """Create a new network
+
+    .. :quickref: Network; Create new network
+    """
     return create_generic_model(models.Network, mandatory_fields=(
         'vlan_name', 'vlan_id', 'address', 'first_ip', 'last_ip', 'scope'))
 
@@ -53,6 +67,10 @@ def create_network():
 @bp.route('/interfaces')
 @jwt_required
 def get_interfaces():
+    """Return interfaces
+
+    .. :quickref: Network; Get interfaces
+    """
     return get_generic_model(models.Interface,
                              order_by=models.Interface.ip)
 
@@ -61,7 +79,10 @@ def get_interfaces():
 @jwt_required
 @jwt_groups_accepted('admin', 'create')
 def create_interface():
-    """Create a new interface"""
+    """Create a new interface
+
+    .. :quickref: Network; Create new interface
+    """
     # The validate_interfaces method from the Network class is called when
     # setting interface.network. This is why we don't pass network_id here
     # but network (as vlan_name string)
@@ -71,6 +92,10 @@ def create_interface():
 @bp.route('/hosts')
 @jwt_required
 def get_hosts():
+    """Return hosts
+
+    .. :quickref: Network; Get hosts
+    """
     return get_generic_model(models.Host,
                              order_by=models.Host.name)
 
@@ -79,13 +104,20 @@ def get_hosts():
 @jwt_required
 @jwt_groups_accepted('admin', 'create')
 def create_host():
-    """Create a new host"""
+    """Create a new host
+
+    .. :quickref: Network; Create new host
+    """
     return create_generic_model(models.Host, mandatory_fields=('name',))
 
 
 @bp.route('/macs')
 @jwt_required
 def get_macs():
+    """Return mac addresses
+
+    .. :quickref: Network; Get mac addresses
+    """
     return get_generic_model(models.Mac,
                              order_by=models.Mac.address)
 
@@ -94,5 +126,9 @@ def get_macs():
 @jwt_required
 @jwt_groups_accepted('admin', 'create')
 def create_macs():
+    """Create a new mac address
+
+    .. :quickref: Network; Create new mac address
+    """
     return create_generic_model(models.Mac,
                                 mandatory_fields=('address',))
diff --git a/app/api/user.py b/app/api/user.py
index ba22298..e8b3e10 100644
--- a/app/api/user.py
+++ b/app/api/user.py
@@ -23,6 +23,13 @@ bp = Blueprint('user_api', __name__)
 @bp.route('/users')
 @jwt_required
 def get_users():
+    """Return users information
+
+    .. :quickref: User; Get users information
+
+    :status 200: users found
+    :returns: :py:class:`~app.models.User`
+    """
     return get_generic_model(models.User,
                              order_by=models.User.username)
 
@@ -31,13 +38,28 @@ def get_users():
 @jwt_required
 @jwt_groups_accepted('admin')
 def create_user():
-    """Create a new user"""
+    """Create a new user
+
+    .. :quickref: User; Create new user
+
+    :status 201: user created
+    :returns: :py:class:`~app.models.User`
+    """
     return create_generic_model(models.User, mandatory_fields=(
         'username', 'display_name', 'email'))
 
 
 @bp.route('/login', methods=['POST'])
 def login():
+    """Login
+
+    .. :quickref: User; Login
+
+    :status 201: login successful
+    :status 422: missing mandatory field
+    :status 401: invalid credentials
+    :returns: access_token
+    """
     data = request.get_json()
     if data is None:
         raise utils.CSEntryError('Body should be a JSON object')
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..51d5b72
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+SPHINXPROJ    = CSEntry
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 0000000..9923ffc
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,30 @@
+API documentation
+=================
+
+Summary
+-------
+
+.. qrefflask:: wsgi:app
+   :modules: app.api.user, app.api.inventory, app.api.network
+   :order: path
+
+User
+----
+
+.. autoflask:: wsgi:app
+   :modules: app.api.user
+   :order: path
+
+Inventory
+---------
+
+.. autoflask:: wsgi:app
+   :modules: app.api.inventory
+   :order: path
+
+Network
+-------
+
+.. autoflask:: wsgi:app
+   :modules: app.api.network
+   :order: path
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..54ad156
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# CSEntry documentation build configuration file, created by
+# sphinx-quickstart on Sun Feb  4 20:26:41 2018.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# 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('..'))
+
+
+# -- 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.intersphinx',
+    'sphinx.ext.viewcode',
+    'sphinxcontrib.httpdomain',
+    'sphinxcontrib.autohttp.flask',
+    'sphinxcontrib.autohttp.flaskqref',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# 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'
+
+# General information about the project.
+project = 'CSEntry'
+copyright = '2018, Benjamin Bertrand'
+author = 'Benjamin Bertrand'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = os.popen('git describe').read().strip()
+# The full version, including alpha/beta/rc tags.
+release = version
+
+# 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 patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- 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 = 'alabaster'
+
+# 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 = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# This is required for the alabaster theme
+# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
+html_sidebars = {
+    '**': [
+        'relations.html',  # needs 'show_related': True theme option to display
+        'searchbox.html',
+    ]
+}
+
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'CSEntrydoc'
+
+
+# -- 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, 'CSEntry.tex', 'CSEntry Documentation',
+     'Benjamin Bertrand', '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, 'csentry', 'CSEntry 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, 'CSEntry', 'CSEntry Documentation',
+     author, 'CSEntry', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/3/': None}
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..60ffce0
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,28 @@
+.. CSEntry documentation master file, created by
+   sphinx-quickstart on Sun Feb  4 20:26:41 2018.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to CSEntry's documentation!
+===================================
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+
+
+API
+---
+
+.. toctree::
+   :maxdepth: 2
+
+   api
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
-- 
GitLab