The importance of good documentation for our project is highly rated and regarded for a reason. Plain and simple, it enables others to understand, use and enhance our code.
But here’s another truth and let’s accept it, we as developers suck at it! And it’s okay because we are not authors or technical writers. We just like to build things and code!
So, how can we continue to do what we are good at as well as create something that is easily accepted by others (developers/users)?
Right! We are developers so we obviously have some automated tools at our doorstep. Let’s embrace one of them called sphinx (authored by Georg Brandl [respect!])
Six steps to mastering documentation for your python code
- Step1: Python project structure
- Step2: Code comments and README.md
- Step3: Installing and Setting up
- Step4: Generating the documentation
- Step5: Uploading to github
- Step6: Hosting documentation online
Step1: Python project structure
For the sake of clarity and more importantly not fighting the “module not found” error :), I will be using the following project structure:
[root] --pythonic_documentation --pythonic_documentation/docs/ --pythonic_documentation/resources/ --pythonic_documentation/src/ --pythonic_documentation/src/___init___.py --pythonic_documentation/src/try_logging.py --pythonic_documentation/src/...other files... --pythonic_documentation/test/ --.....
In this structure please make note of the following:
- docs/ is an empty folder at the beginning. It will contain:
…1.1 Source files (.rst and .md files)
…1.2 Build files (.html) - src/ contains our python code
- resources/ may contain other supporting files like config files
Step2: Code comments and README.md
github has done a great job at making us all used-to having the bare minimum README.md. So, I wont delve into it.
For the api reference, the inline comments in our code are transformed into the api doc. I assume we should have some aspects of this covered in your code. However, if you are unsure, I highly recommend going through this 1 min read:
https://www.linkedin.com/pulse/how-comment-your-code-python-sample-harleen-mann/
Step3: Installing and Setting up
We will be using the following two packages for automated document generation
1. sphinx:
….is the heart of generating the documentation. By default, it reads the .rst files instead of .md
2. recommonmark:
….is a utility that works with sphinx to help process .md files
Step3.1: Install the required packages:
> cd docs\ > pip install sphinx > pip install recommonmark
Step3.2: Setup sphinx:
> sphinx-quickstart ## Use all defaults when asked for except for the following for which you should choose 'y' autodoc: automatically insert docstrings from modules (y/n) [n]: y
You will see a series of folders and files getting created in your docs\ folder. This is normal 🙂
Step3.3: Include the following to conf.py:
This is done to include the project in the import path, for auto-api to pick up comments from our code
This also enables the CommonMarkParser that helps sphinx read the .md files
import os import sys sys.path.insert(0, os.path.abspath('..')) source_parsers = { '.md': 'recommonmark.parser.CommonMarkParser', }
Step3.4: Amend the following to conf.py:
This is done to process .md files with sphinx
source_suffix = ['.rst', '.md'] # include .md here
Step3.5: Include the following to make.bat conf.py:
This is done to copy the README.md to our docs\ folder to be picked up by
“`bash
if exist "..\README.md" (COPY /Y ..\README.md README.md)
“`
from shutil import copy from pathlib import Path from_path = (Path(".") / "../README.md" ).resolve().__str__() copy(from_path, '.')
Step3.6: Amend the following to index.rst:
This is done to list the README file on our index.html page
.. toctree:: :maxdepth: 1 :caption: Contents: README <README.md>
Step4: Generating the documentation (html files in our case)
The documentation that we will generate using sphinx will contain the following parts:
1. Various html files with the top page being index.html
2. A TOC side bar
3. A contents section that points to other html pages
4. An index and a module index (this is the api document!)
Generating this is as simple as:
> sphinx-apidoc -f -o . .. > make html
The resulting html files can be found in docs_build\html
Go ahead, open the index.hmll file in your fav browser and see for your self.
Step5: Uploading to github
This should be pretty straight forward, but in case you aren’t comfortable find the code below:
> cd C:\......\pythonic_documentation\ # now go to github.com # create new repo. keep all defaults. I named it 'pythonic_documentation' # follow the instruction from github, in my case these are: > git init # i also created a .gitignore file so that I don't include resource and other libraries not required # my .gitignore looks like: # venv/* # .idea/* # docs/_build/* # docs/_static/* # docs/_templates/* > git add . > git commit -m"first commit" > git remote add origin https://github.com/mannharleen/pythonic_documentation.git > git push -u origin master
This hosts our code on github. For me, at https://github.com/mannharleen/pythonic_documentation.git
Step6: Hosting documentation online
We will use https://readthedocs.io for this
Assuming, you have set up an account on readthedocs, import your project and
click-> build
And there, you have your documentation online !
For me, the URL is https://pythonic-documentation.readthedocs.io/en/latest/
Check the link for the api doc:
https://pythonic-documentation.readthedocs.io/en/latest/py-modindex.html
Some gotchas:
Pay close attention to the advanced setting of your project in readthedocs. Go to: project > Admin > Advanced Settings
For instance,
1. Make sure the python version is correct
2. Requirements file is specified if you require a new virtual environment
Additionally, some issues may occur if your project depends on other packages that require C libraries (e.g. numpy). In which case, make use of the mock library and include the following code in your conf.py file:
from mock import Mock as MagicMock class Mock(MagicMock): @classmethod def __getattr__(cls, name): return MagicMock() MOCK_MODULES = ['numpy', 'scipy', 'scipy.linalg', 'scipy.signal'] sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)