diff --git a/docs/pages/developers/refraction.py b/docs/pages/developers/refraction.py new file mode 100644 index 00000000..2e2c4e96 --- /dev/null +++ b/docs/pages/developers/refraction.py @@ -0,0 +1,32 @@ +# example/refraction.py + +import numpy as np + + +def snell(theta_inc, n1, n2): + """ + Compute the refraction angle using Snell's Law. + + See https://en.wikipedia.org/wiki/Snell%27s_law + + Parameters + ---------- + theta_inc : float + Incident angle in radians. + n1, n2 : float + The refractive index of medium of origin and destination medium. + + Returns + ------- + theta : float + refraction angle + + Examples + -------- + A ray enters an air--water boundary at pi/4 radians (45 degrees). + Compute exit angle. + + >>> snell(np.pi/4, 1.00, 1.33) + 0.5605584137424605 + """ + return np.arcsin(n1 / n2 * np.sin(theta_inc)) diff --git a/docs/pages/developers/test_examples.py b/docs/pages/developers/test_examples.py new file mode 100644 index 00000000..7c925101 --- /dev/null +++ b/docs/pages/developers/test_examples.py @@ -0,0 +1,26 @@ +# example/tests/test_examples.py + +import numpy as np +from ..refraction import snell +# (The above is equivalent to `from example.refraction import snell`. +# Read on for why.) + + +def test_perpendicular(): + # For any indexes, a ray normal to the surface should not bend. + # We'll try a couple different combinations of indexes.... + + actual = snell(0, 2.00, 3.00) + expected = 0 + assert actual == expected + + actual = snell(0, 3.00, 2.00) + expected = 0 + assert actual == expected + + +def test_air_water(): + n_air, n_water = 1.00, 1.33 + actual = snell(np.pi/4, n_air, n_water) + expected = 0.5605584137424605 + assert np.allclose(actual, expected) diff --git a/docs/pages/developers/the-code-itself.md b/docs/pages/developers/the-code-itself.md new file mode 100644 index 00000000..c197f042 --- /dev/null +++ b/docs/pages/developers/the-code-itself.md @@ -0,0 +1,372 @@ +# The Code Itself + +In this section you will: + +- Put some scientific code in your new Python package. +- Update your package's list of dependencies in `requirements.txt`. +- Write a test and run the test suite. +- Use a "linter" and style-checker. +- Commit your changes to git and sync your changes with GitHub. + +## A simple function with inline documentation + +Let's write a simple function that encodes +[Snell's Law](https://en.wikipedia.org/wiki/Snell%27s_law) and include it in +our Python package. + +Look again at the directory structure. + +```none +example/ +├── .flake8 +├── .gitattributes +├── .gitignore +├── .travis.yml +├── AUTHORS.rst +├── CONTRIBUTING.rst +├── LICENSE +├── MANIFEST.in +├── README.rst +├── docs +│   ├── Makefile +│   ├── build +│   ├── make.bat +│   └── source +│   ├── _static +│   │   └── .placeholder +│   ├── _templates +│   ├── conf.py +│   ├── index.rst +│   ├── installation.rst +│   ├── release-history.rst +│   └── usage.rst +├── example +│   ├── __init__.py +│   ├── _version.py +│   └── tests +│   └── test_examples.py +├── requirements-dev.txt +├── requirements.txt +├── setup.cfg +├── setup.py +└── versioneer.py +``` + +Our scientific code should go in the `example/` subdirectory, next to +`__init__.py`. Let's make a new file in that directory named +`refraction.py`, meaning our new layout will be: + +```none +├── example +│   ├── __init__.py +│   ├── _version.py +│   ├── refraction.py +│   └── tests +│   └── test_examples.py +``` + +This is our new file. You may follow along exactly or, instead, make a file +with a different name and your own scientific function. + +```{literalinclude} refraction.py +``` + +Notice that this example includes inline documentation --- a "docstring". This +is extremely useful for collaborators, and the most common collaborator is +Future You! + +Further, by following the +[numpydoc standard](https://numpydoc.readthedocs.io/en/latest/format.html), +we will be able to automatically generate nice-looking HTML documentation +later. Notable features: + +- There is a succinct, one-line summary of the function's purpose. It must one + line. + +- (Optional) There is an paragraph elaborating on that summary. + +- There is a section listing input parameters, with the structure + + ```none + parameter_name : parameter_type + optional description + ``` + + Note that space before the `:`. That is part of the standard. + +- Similar parameters may be combined into one entry for brevity's sake, as we + have done for `n1, n2` here. + +- There is a section describing what the function returns. + +- (Optional) There is a section of one or more examples. + +We will revisit docstrings in the section on {doc}`writing-docs`. + +## Update Requirements + +Notice that our package has a third-party dependency, numpy. We should +update our package's `requirements.txt`. + +```text +# requirements.txt + +# List required packages in this file, one per line. +numpy +``` + +Our cookiecutter configured `setup.py` to read this file. It will ensure that +numpy is installed when our package is installed. + +We can test it by reinstalling the package. + +```bash +python3 -m pip install -e . +``` + +## Try it + +Try importing and using the function. + +```python +>>> from example.refraction import snell +>>> import numpy as np +>>> snell(np.pi/4, 1.00, 1.33) +1.2239576240104186 +``` + +The docstring can be viewed with {func}`help`. + +```python +>>> help(snell) +``` + +Or, as a shortcut, use `?` in IPython/Jupyter. + +```{eval-rst} +.. ipython:: python + :verbatim: + + snell? +``` + +## Run the Tests + +You should add a test right away while the details are still fresh in mind. +Writing tests encourages you to write modular, reusable code, which is easier +to test. + +The cookiecutter template included an example test suite with one test: + +```python +# example/tests/test_examples.py + +def test_one_plus_one_is_two(): + assert 1 + 1 == 2 +``` + +Before writing our own test, let's practice running that test to check that +everything is working. + +:::{important} +We assume you have installed the "development requirements," as covered +in {doc}`preliminaries`. If you are not sure whether you have, there is no +harm in running this a second time: + +```bash +python3 -m pip install --upgrade -r requirements-dev.txt +``` +::: + +```bash +python3 -m pytest +``` + +This walks through all the directories and files in our package that start with +the word 'test' and collects all the functions whose name also starts with +`test`. Currently, there is just one, `test_one_plus_one_is_two`. +`pytest` runs that function. If no exceptions are raised, the test passes. + +The output should look something like this: + +```bash +======================================== test session starts ======================================== +platform darwin -- Python 3.6.4, pytest-3.6.2, py-1.5.4, pluggy-0.6.0 +benchmark: 3.1.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) +rootdir: /private/tmp/test11/example, inifile: +plugins: xdist-1.22.2, timeout-1.2.1, rerunfailures-4.0, pep8-1.0.6, lazy-fixture-0.3.0, forked-0.2, benchmark-3.1.1 +collected 1 item + +example/tests/test_examples.py . [100%] + +===================================== 1 passed in 0.02 seconds ====================================== +``` + +:::{note} +The output of `pytest` is customizable. Commonly useful command-line +arguments include: + +- `-v` verbose +- `-s` Do not capture stdout/err per test. +- `-k EXPRESSION` Filter tests by pattern-matching test name. + +Consult the [pytest documentation](https://docs.pytest.org/en/latest/) +for more. +::: + +## Write a Test + +Let's add a test to `test_examples.py` that exercises our `snell` function. +We can delete `test_one_plus_one_is_two` now. + +```{literalinclude} test_examples.py +``` + +Things to notice: + +- It is sometime useful to put multiple `assert` statements in one test. You + should make a separate test for each *behavior* that you are checking. When a + monolithic, multi-step tests fails, it's difficult to figure out why. +- When comparing floating-point numbers (as opposed to integers) you should not + test for exact equality. Use {func}`numpy.allclose`, which checks for + equality within a (configurable) tolerance. Numpy provides several + [testing utilities](https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.testing.html), + which should always be used when testing numpy arrays. +- Remember that the names of all test modules and functions must begin with + `test` or they will not be picked up by pytest! + +See {doc}`advanced-testing` for more. + +## "Lint": Check for suspicious-looking code + +A [linter]() is a tool that +analyzes code to flag potential errors. For example, it can catch variables you +defined by never used, which is likely a spelling error. + +The cookiecutter configured `flake8` for this purpose. Flake8 checks for +"lint" and also enforces the standard Python coding style, +[PEP8](https://www.python.org/dev/peps/pep-0008/?#introduction). Enforcing +consistent style helps projects stay easy to read and maintain as they grow. +While not all projects strictly enfore PEP8, we generally recommend it. + +:::{important} +We assume you have installed the "development requirements," as covered +in {doc}`preliminaries`. If you are not sure whether you have, there is no +harm in running this a second time: + +```bash +python3 -m pip install --upgrade -r requirements-dev.txt +``` +::: + +```bash +python3 -m flake8 +``` + +This will list linting or stylistic errors. If there is no output, all is well. +See the [flake8 documentation](http://flake8.pycqa.org/en/latest/) for more. + +## Commit and Push Changes + +Remember to commit your changes to version control and push them up to GitHub. + +:::{important} +The following is a quick reference that makes some assumptions about your +local configuration and workflow. + +This usage is part of a workflow named *GitHub flow*. See +[this guide](https://guides.github.com/introduction/flow/) for more. +::: + +Remember that at any time you may use `git status` to check which branch +you are currently on and which files have uncommitted changes. Use `git diff` +to review the content of those changes. + +1. If you have not already done so, create a new "feature branch" for this work + with some descriptive name. + + ```bash + git checkout master # Starting from the master branch... + git checkout -b add-snell-function # ...make a new branch. + ``` + +2. Stage changes to be committed. In our example, we have created one new file + and changed an existing one. We `git add` both. + + ```bash + git add example/refraction.py + git add example/tests/test_examples.py + ``` + +3. Commit changes. + + ```bash + git commit -m "Add snell function and tests." + ``` + +4. Push changes to remote repository on GitHub. + + ```bash + git push origin add-snell-function + ``` + +5. Repeat steps 2-4 until you are happy with this feature. + +6. Create a Pull Request --- or merge to master. + + When you are ready for collaborators to review your work and consider merging + the `add-snell-function` branch into the `master` branch, + [create a pull request](https://help.github.com/articles/creating-a-pull-request). + Even if you presently have no collaborators, going through this process is a + useful way to document the history of changes to the project for any *future* + collaborators (and Future You). + + However, if you are in the early stages of just getting a project up and you + are the only developer, you might skip the pull request step and merge the + changes yourself. + + ```bash + git checkout master + # Ensure local master branch is up to date with remote master branch. + git pull --ff-only origin master + # Merge the commits from add-snell-function into master. + git merge add-snell-function + # Update the remote master branch. + git push origin master + ``` + +## Multiple modules + +We created just one module, `example.refraction`. We might eventually grow a +second module --- say, `example.utils`. Some brief advice: + +- When in doubt, resist the temptation to grow deep taxonomies of modules and + sub-packages, lest it become difficult for users and collaborators to + remember where everything is. The Python built-in libraries are generally + flat. + +- When making intra-package imports, we recommend relative imports. + + This works: + + ```bash + # example/refraction.py + + from example import utils + from example.utils import some_function + ``` + + but this is equivalent, and preferred: + + ```bash + # example/refraction.py + + from . import utils + from .utils import some_function + ``` + + For one thing, if you change the name of the package in the future, you won't + need to update this file. + +- Take care to avoid circular imports, wherein two modules each import the + other. diff --git a/docs/pages/developers/writing-docs.md b/docs/pages/developers/writing-docs.md new file mode 100644 index 00000000..6e660ef0 --- /dev/null +++ b/docs/pages/developers/writing-docs.md @@ -0,0 +1,291 @@ +# Writing Documentation + +In this section you will: + +- Generate HTML documentation using Sphinx, starting from a working example + provided by the cookiecutter template. +- Edit `usage.rst` to add API documentation and narrative documentation. +- Learn how to incorporate code examples, IPython examples, matplotlib plots, + and typeset math. + +## Build the docs + +Almost all scientific Python projects use the +[Sphinx documentation generator](http://www.sphinx-doc.org/). +The cookiecutter template provided a working example with some popular +extensions installed and some sample pages. + +```none +example/ +(...) +├── docs +│   ├── Makefile +│   ├── build +│   ├── make.bat +│   └── source +│   ├── _static +│   │   └── .placeholder +│   ├── _templates +│   ├── conf.py +│   ├── index.rst +│   ├── installation.rst +│   ├── release-history.rst +│   └── usage.rst +(...) +``` + +The `.rst` files are source code for our documentation. To build HTML pages +from this source, run: + +```bash +make -C docs html +``` + +You should see some log message ending in `build succeeded.` + +This output HTML will be located in `docs/build/html`. In your Internet +browser, open `file://.../docs/build/html/index.html`, where `...` is the +path to your project directory. If you aren't sure sure where that is, type +`pwd`. + +## Update the docs + +The source code for the documentation is located in `docs/source/`. +Sphinx uses a markup language called ReStructured Text (.rst). We refer you to +[this primer](http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) +to learn how to denote headings, links, lists, cross-references, etc. + +Sphinx formatting is sensitive to whitespace and generally quite picky. We +recommend running `make -C docs html` often to check that the documentation +builds successfully. Remember to commit your changes to git periodically. + +Good documentation includes both: + +- API (Application Programming Interface) documentation, listing every public + object in the library and its usage +- Narrative documentation interleaving prose and code examples to explain how + and why a library is meant to be used + +## API Documentation + +Most the work of writing good API documentation goes into writing good, +accurate docstrings. Sphinx can scrape that content and generate HTML from it. +Again, most scientific Python libraries use the +[numpydoc standard](https://numpydoc.readthedocs.io/en/latest/format.html), +which looks like this: + +```{literalinclude} refraction.py +``` + +### Autodoc + +In an rst file, such as `docs/source/usage.rst`, we can write: + +```rst +.. autofunction:: example.refraction.snell +``` + +which renders in HTML like so: + +```{eval-rst} +.. autofunction:: example.refraction.snell + :noindex: +``` + +From here we refer you to the +[sphinx autodoc documentation](http://www.sphinx-doc.org/en/stable/ext/autodoc.html). + +### Autosummary + +If you have many related objects to document, it may be better to display them +in a table. Each row will include the name, the signature (optional), and the +one-line description from the docstring. + +In rst we can write: + +```rst +.. autosummary:: + :toctree: generated/ + + example.refraction.snell +``` + +which renders in HTML like so: + +```{eval-rst} +.. autosummary:: + :toctree: generated/ + + example.refraction.snell +``` + +It links to the full rendered docstring on a separate page that is +automatically generated. + +From here we refer you to the +[sphinx autosummary documentation](http://www.sphinx-doc.org/en/stable/ext/autosummary.html). + +## Narrative Documentation + +### Code Blocks + +Code blocks can be interspersed with narrative text like this: + +```rst +Scientific libraries conventionally use radians. Numpy provides convenience +functions for converting between radians and degrees. + +.. code-block:: python + + import numpy as np + + + np.deg2rad(90) # pi / 2 + np.rad2deg(np.pi / 2) # 90.0 +``` + +which renders in HTML as: + +Scientific libraries conventionally use radians. Numpy provides convenience +functions for converting between radians and degrees. + +```python +import numpy as np + + +np.deg2rad(90) # pi / 2 +np.rad2deg(np.pi / 2) # 90.0 +``` + +To render short code expressions inline, surround them with back-ticks. This: + +```rst +Try ``snell(0, 1, 1.33)``. +``` + +renders in HTML as: + +Try `snell(0, 1, 1.33)`. + +### Embedded Scripts + +For lengthy examples with tens of lines or more, it can be convenient to embed +the content of a .py file rather than writing it directly into the +documentation. + +This can be done using the directive + +```rest +.. literalinclude:: examples/some_example.py +``` + +where the path is given relative to the current file's path. Thus, relative to +the repository's root directory, the path to this example script would be +`docs/source/examples/some_example.py`. + +From here we refer you to the +[sphinx code example documentation](http://www.sphinx-doc.org/en/stable/markup/code.html). + +To go beyond embedded scripts to a more richly-featured example gallery that +shows scripts and their outputs, we encourage you to look at +[sphinx-gallery](https://sphinx-gallery.github.io/). + +### IPython Examples + +IPython's sphinx extension, which is included by the cookiecutter template, +makes it possible to execute example code and capture its output when the +documentation is built. This rst code: + +```rst +.. ipython:: python + + 1 + 1 +``` + +renders in HTML as: + +```{eval-rst} +.. ipython:: python + + 1 + 1 +``` + +From here we refer you to the +[IPython sphinx directive documentation](https://ipython.org/ipython-doc/rel-0.13.2/development/ipython_directive.html). + +### Plots + +Matplotlib's sphinx extension, which is included by the cookiecutter template, +makes it possible to display matplotlib figures in line. This rst code: + +```rst +.. plot:: + + import matplotlib.pyplot as plt + fig, ax = plt.subplots() + ax.plot([1, 1, 2, 3, 5, 8]) +``` + +renders in HTML as: + +```{eval-rst} +.. plot:: + + import matplotlib.pyplot as plt + fig, ax = plt.subplots() + ax.plot([1, 1, 2, 3, 5, 8]) +``` + +From here we refer you to the +[matplotlib plot directive documentation](https://matplotlib.org/devel/plot_directive.html). + +### Math (LaTeX) + +Sphinx can render LaTeX typeset math in the browser (using +[MathJax](https://www.mathjax.org/)). This rst code: + +```rst +.. math:: + + \int_0^a x\,dx = \frac{1}{2}a^2 +``` + +renders in HTML as: + +$$ +\int_0^a x\,dx = \frac{1}{2}a^2 +$$ + +This notation can also be used in docstrings. For example, we could add +the equation of Snell's Law to the docstring of +{func}`~example.refraction.snell`. + +Math can also be written inline. This rst code: + +```rst +The value of :math:`\pi` is 3.141592653.... +``` + +renders in HTML as: + +> The value of $\pi$ is 3.141592653.... + +### Referencing Documented Objects + +You can create links to documented functions like so: + +```rst +The :func:`example.refraction.snell` function encodes Snell's Law. +``` + +The {func}`example.refraction.snell` function encodes Snell's Law. + +Adding a `~` omits the module path from the link text. + +```rst +The :func:`~example.refraction.snell` function encodes Snell's Law. +``` + +The {func}`~example.refraction.snell` function encodes Snell's Law. + +See [the Sphinx documentation](http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) for more.