|
|
.. _development:
|
|
|
|
|
|
==============================
|
|
|
IPython development guidelines
|
|
|
==============================
|
|
|
|
|
|
|
|
|
Overview
|
|
|
========
|
|
|
|
|
|
Currently, IPython's development tree contains all of the code that used to be
|
|
|
part of IPython since the project's inception in 2001, plus all of the effort
|
|
|
that was known for a while (roughly 2004-2008) as `IPython1'. The IPyhton1
|
|
|
development was meant to be an effort to:
|
|
|
|
|
|
1. Clean up the existing codebase and write lots of tests.
|
|
|
|
|
|
2. Separate the core functionality of IPython from the terminal to enable
|
|
|
IPython to be used from within a variety of GUI applications.
|
|
|
|
|
|
3. Implement a system for interactive parallel computing.
|
|
|
|
|
|
While the third goal may seem a bit unrelated to the main focus of IPython, it
|
|
|
turns out that the technologies required for this goal are nearly identical
|
|
|
with those required for goal two. This is the main reason the interactive
|
|
|
parallel computing capabilities are being put into IPython proper. Currently
|
|
|
the third of these goals is furthest along.
|
|
|
|
|
|
This document describes IPython from the perspective of developers.
|
|
|
|
|
|
|
|
|
Project organization
|
|
|
====================
|
|
|
|
|
|
Subpackages
|
|
|
-----------
|
|
|
|
|
|
IPython is organized into semi self-contained subpackages. Each of the
|
|
|
subpackages will have its own:
|
|
|
|
|
|
- **Dependencies**. One of the most important things to keep in mind in
|
|
|
partitioning code amongst subpackages, is that they should be used to cleanly
|
|
|
encapsulate dependencies.
|
|
|
|
|
|
- **Tests**. Each subpackage shoud have its own ``tests`` subdirectory that
|
|
|
contains all of the tests for that package. For information about writing
|
|
|
tests for IPython, see the `Testing System`_ section of this document.
|
|
|
|
|
|
- **Configuration**. Each subpackage should have its own ``config``
|
|
|
subdirectory that contains the configuration information for the components
|
|
|
of the subpackage. For information about how the IPython configuration
|
|
|
system works, see the `Configuration System`_ section of this document.
|
|
|
|
|
|
- **Scripts**. Each subpackage should have its own ``scripts`` subdirectory
|
|
|
that contains all of the command line scripts associated with the subpackage.
|
|
|
|
|
|
|
|
|
Installation and dependencies
|
|
|
-----------------------------
|
|
|
|
|
|
IPython will not use `setuptools`_ for installation. Instead, we will use
|
|
|
standard ``setup.py`` scripts that use `distutils`_. While there are a number a
|
|
|
extremely nice features that `setuptools`_ has (like namespace packages), the
|
|
|
current implementation of `setuptools`_ has performance problems, particularly
|
|
|
on shared file systems. In particular, when Python packages are installed on
|
|
|
NSF file systems, import times become much too long (up towards 10 seconds).
|
|
|
|
|
|
Because IPython is being used extensively in the context of high performance
|
|
|
computing, where performance is critical but shared file systems are common, we
|
|
|
feel these performance hits are not acceptable. Thus, until the performance
|
|
|
problems associated with `setuptools`_ are addressed, we will stick with plain
|
|
|
`distutils`_. We are hopeful that these problems will be addressed and that we
|
|
|
will eventually begin using `setuptools`_. Because of this, we are trying to
|
|
|
organize IPython in a way that will make the eventual transition to
|
|
|
`setuptools`_ as painless as possible.
|
|
|
|
|
|
Because we will be using `distutils`_, there will be no method for
|
|
|
automatically installing dependencies. Instead, we are following the approach
|
|
|
of `Matplotlib`_ which can be summarized as follows:
|
|
|
|
|
|
- Distinguish between required and optional dependencies. However, the required
|
|
|
dependencies for IPython should be only the Python standard library.
|
|
|
|
|
|
- Upon installation check to see which optional dependencies are present and
|
|
|
tell the user which parts of IPython need which optional dependencies.
|
|
|
|
|
|
It is absolutely critical that each subpackage of IPython has a clearly
|
|
|
specified set of dependencies and that dependencies are not carelessly
|
|
|
inherited from other IPython subpackages. Furthermore, tests that have certain
|
|
|
dependencies should not fail if those dependencies are not present. Instead
|
|
|
they should be skipped and print a message.
|
|
|
|
|
|
.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
|
|
|
.. _distutils: http://docs.python.org/lib/module-distutils.html
|
|
|
.. _Matplotlib: http://matplotlib.sourceforge.net/
|
|
|
|
|
|
Specific subpackages
|
|
|
--------------------
|
|
|
|
|
|
``core``
|
|
|
This is the core functionality of IPython that is independent of the
|
|
|
terminal, network and GUIs. Most of the code that is in the current
|
|
|
IPython trunk will be refactored, cleaned up and moved here.
|
|
|
|
|
|
``kernel``
|
|
|
The enables the IPython core to be expose to a the network. This is
|
|
|
also where all of the parallel computing capabilities are to be found.
|
|
|
|
|
|
``config``
|
|
|
The configuration package used by IPython.
|
|
|
|
|
|
``frontends``
|
|
|
The various frontends for IPython. A frontend is the end-user application
|
|
|
that exposes the capabilities of IPython to the user. The most basic
|
|
|
frontend will simply be a terminal based application that looks just like
|
|
|
today 's IPython. Other frontends will likely be more powerful and based
|
|
|
on GUI toolkits.
|
|
|
|
|
|
``notebook``
|
|
|
An application that allows users to work with IPython notebooks.
|
|
|
|
|
|
``tools``
|
|
|
This is where general utilities go.
|
|
|
|
|
|
|
|
|
Version control
|
|
|
===============
|
|
|
|
|
|
All IPython development today is done using the `Bazaar`__ system for
|
|
|
distributed version control and the `Launchpad`__ site for code hosting and bug
|
|
|
tracking. This makes it very easy for anyone to contribute code to IPython.
|
|
|
|
|
|
.. __: http://bazaar-vcs.org
|
|
|
.. __: http://launchpad.net/ipython
|
|
|
|
|
|
If you are interested in contributing to IPython, you should familiarize a bit
|
|
|
with how to use bazaar, but this document gives you a concise set of steps to
|
|
|
get started. If you are going to become heavily involved with creating code
|
|
|
for IPython you'll eventually want to have a Launchpad account (free) so you
|
|
|
can publish your own code on the site for review and merging, but small
|
|
|
contributions can be simply sent via email to the IPython list as patches.
|
|
|
|
|
|
Start by creating what Bazaar calls a `shared repository`_:
|
|
|
|
|
|
.. sourcecode:: bash
|
|
|
|
|
|
# You can choose any name you want instead of "ipython-repo"
|
|
|
bzr init-repo ipython-repo
|
|
|
|
|
|
.. _shared repository: http://bazaar-vcs.org/SharedRepositoryTutorial
|
|
|
|
|
|
This creates an empty repository where a lot of related branches can be kept,
|
|
|
and they all reuse common storage. This way, many operations are very fast and
|
|
|
take up less space. Now, go to the newly created repository and make a local
|
|
|
branch of the public trunk:
|
|
|
|
|
|
.. sourcecode:: bash
|
|
|
|
|
|
cd ipython-repo
|
|
|
# This makes a local copy of the public trunk called "trunk-lp"
|
|
|
bzr branch lp:ipython trunk-lp
|
|
|
|
|
|
The ``trunk-lp`` branch is meant to always be a pristine copy of the public
|
|
|
trunk. From here, make a personal development copy of the public trunk, where
|
|
|
you'll make your changes:
|
|
|
|
|
|
.. sourcecode:: bash
|
|
|
|
|
|
bzr branch trunk-lp trunk-dev
|
|
|
|
|
|
Now, your working area looks like this::
|
|
|
|
|
|
maqroll[ipython-repo]> ls -a
|
|
|
./ ../ .bzr/ trunk-dev/ trunk-lp/
|
|
|
|
|
|
The ``.bzr`` directory is the Bazaar storage area, and you now have both the
|
|
|
reference copy of the public trunk and one working copy for your own
|
|
|
development. You can later make further branches as needed (a common workflow
|
|
|
is to make branches to contain specific feature implementations until they are
|
|
|
ready to be merged).
|
|
|
|
|
|
The typical work cycle will be to make changes in ``trunk-dev`` and then commit
|
|
|
those changes as often as needed:
|
|
|
|
|
|
.. sourcecode:: bash
|
|
|
|
|
|
cd trunk-dev
|
|
|
# ... implement cool new feature...
|
|
|
bzr commit -m "Commit message goes here."
|
|
|
|
|
|
Please note that since we now don't use an old-style linear ChangeLog (that
|
|
|
tends to cause problems with distributed version control systems), you should
|
|
|
ensure that your log messages are reasonably detailed. For non-trivial
|
|
|
changes, use a docstring-like approach in the commit messages (including the
|
|
|
second line being left *blank*). Type ``bzr commit`` *without* the ``-m``
|
|
|
switch, and Bazaar will open an editor where you can type a more detailed
|
|
|
message::
|
|
|
|
|
|
Single line summary of changes being committed.
|
|
|
|
|
|
- more details when warranted ...
|
|
|
- including crediting outside contributors if they sent the
|
|
|
code/bug/idea!
|
|
|
|
|
|
If we couple this with a policy of making single commits for each reasonably
|
|
|
atomic change, the bzr log should give an excellent view of the project, and
|
|
|
the ``--short`` log option becomes a nice summary.
|
|
|
|
|
|
As you work on the branch, it's a good idea to frequently keep your copy of the
|
|
|
trunk updated with respect to Launchpad. This is done via:
|
|
|
|
|
|
.. sourcecode:: bash
|
|
|
|
|
|
cd trunk-lp
|
|
|
bzr pull
|
|
|
|
|
|
Bazaar can then merge any changes that were done to the public trunk into your
|
|
|
local branch via:
|
|
|
|
|
|
.. sourcecode:: bash
|
|
|
|
|
|
cd trunk-dev
|
|
|
bzr merge ../trunk-lp
|
|
|
bzr commit -m"Merged upstream changes"
|
|
|
|
|
|
This workflow ensures that a local copy stays in sync with the public trunk,
|
|
|
while allowing for local development to be pushed back for public review.
|
|
|
|
|
|
Once your changes are ready for review, you can push them to Launchpad where
|
|
|
the IPython team can review them and give you feedback. The first time, use:
|
|
|
|
|
|
.. sourcecode:: bash
|
|
|
|
|
|
bzr push --remember bzr+ssh://<you>@bazaar.launchpad.net/~<you>/ipython/trunk-dev
|
|
|
|
|
|
where ``<you>`` is your Launchpad user name. This will make this branch
|
|
|
publicly visible to all. In subsequent uses you can simply run in the branch:
|
|
|
|
|
|
.. sourcecode:: bash
|
|
|
|
|
|
bzr push
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
Before you can push your code to Launchpad, you need to configure your SSH
|
|
|
keys. You can do this by clicking on ``Change details`` for your profile
|
|
|
and then clicking on the ``SSH Keys`` tab. This should take you to a URL
|
|
|
of the form ``https://launchpad.net/~<you>/+editsshkeys`` with instructions
|
|
|
on how to upload your keys.
|
|
|
|
|
|
Once the branch is publicly visible, using the launchpad interface it can be
|
|
|
marked for merging with the link ``Propose for merging into another branch``,
|
|
|
leaving the default choice of trunk as its target. This way, Launchpad tracks
|
|
|
what changes of this branch are new with respect to the trunk, others in the
|
|
|
team can review it, and merge the changes into the trunk.
|
|
|
|
|
|
The IPython team has a policy of reviewing all code (both from core members of
|
|
|
the team and from external contributors) before merging it. Code should be
|
|
|
clearly documented (as explained further in this guide), clear and well tested.
|
|
|
We will be happy to provide you with feedback for your code to be a great
|
|
|
contribution to the project, and we've found that peer review of code is an
|
|
|
excellent way to improve the quality of everyone's work.
|
|
|
|
|
|
|
|
|
Coding conventions
|
|
|
==================
|
|
|
|
|
|
General
|
|
|
-------
|
|
|
|
|
|
In general, we'll try to follow the standard Python style conventions as
|
|
|
described here:
|
|
|
|
|
|
- `Style Guide for Python Code <http://www.python.org/peps/pep-0008.html>`_
|
|
|
|
|
|
|
|
|
Other comments:
|
|
|
|
|
|
- In a large file, top level classes and functions should be
|
|
|
separated by 2-3 lines to make it easier to separate them visually.
|
|
|
- Use 4 spaces for indentation.
|
|
|
- Keep the ordering of methods the same in classes that have the same
|
|
|
methods. This is particularly true for classes that implement
|
|
|
similar interfaces and for interfaces that are similar.
|
|
|
|
|
|
Naming conventions
|
|
|
------------------
|
|
|
|
|
|
In terms of naming conventions, we'll follow the guidelines from the `Style
|
|
|
Guide for Python Code`_.
|
|
|
|
|
|
For all new IPython code (and much existing code is being refactored), we'll use:
|
|
|
|
|
|
- All ``lowercase`` module names.
|
|
|
|
|
|
- ``CamelCase`` for class names.
|
|
|
|
|
|
- ``lowercase_with_underscores`` for methods, functions, variables and
|
|
|
attributes.
|
|
|
|
|
|
This may be confusing as most of the existing IPython codebase uses a different
|
|
|
convention (``lowerCamelCase`` for methods and attributes). Slowly, we will
|
|
|
move IPython over to the new convention, providing shadow names for backward
|
|
|
compatibility in public interfaces.
|
|
|
|
|
|
There are, however, some important exceptions to these rules. In some cases,
|
|
|
IPython code will interface with packages (Twisted, Wx, Qt) that use other
|
|
|
conventions. At some level this makes it impossible to adhere to our own
|
|
|
standards at all times. In particular, when subclassing classes that use other
|
|
|
naming conventions, you must follow their naming conventions. To deal with
|
|
|
cases like this, we propose the following policy:
|
|
|
|
|
|
- If you are subclassing a class that uses different conventions, use its
|
|
|
naming conventions throughout your subclass. Thus, if you are creating a
|
|
|
Twisted Protocol class, used Twisted's
|
|
|
``namingSchemeForMethodsAndAttributes.``
|
|
|
|
|
|
- All IPython's official interfaces should use our conventions. In some cases
|
|
|
this will mean that you need to provide shadow names (first implement
|
|
|
``fooBar`` and then ``foo_bar = fooBar``). We want to avoid this at all
|
|
|
costs, but it will probably be necessary at times. But, please use this
|
|
|
sparingly!
|
|
|
|
|
|
Implementation-specific *private* methods will use
|
|
|
``_single_underscore_prefix``. Names with a leading double underscore will
|
|
|
*only* be used in special cases, as they makes subclassing difficult (such
|
|
|
names are not easily seen by child classes).
|
|
|
|
|
|
Occasionally some run-in lowercase names are used, but mostly for very short
|
|
|
names or where we are implementing methods very similar to existing ones in a
|
|
|
base class (like ``runlines()`` where ``runsource()`` and ``runcode()`` had
|
|
|
established precedent).
|
|
|
|
|
|
The old IPython codebase has a big mix of classes and modules prefixed with an
|
|
|
explicit ``IP``. In Python this is mostly unnecessary, redundant and frowned
|
|
|
upon, as namespaces offer cleaner prefixing. The only case where this approach
|
|
|
is justified is for classes which are expected to be imported into external
|
|
|
namespaces and a very generic name (like Shell) is too likely to clash with
|
|
|
something else. We'll need to revisit this issue as we clean up and refactor
|
|
|
the code, but in general we should remove as many unnecessary ``IP``/``ip``
|
|
|
prefixes as possible. However, if a prefix seems absolutely necessary the more
|
|
|
specific ``IPY`` or ``ipy`` are preferred.
|
|
|
|
|
|
|
|
|
Documentation
|
|
|
=============
|
|
|
|
|
|
Standalone documentation
|
|
|
------------------------
|
|
|
|
|
|
All standalone documentation should be written in plain text (``.txt``) files
|
|
|
using `reStructuredText`_ for markup and formatting. All such documentation
|
|
|
should be placed in the top level directory ``docs`` of the IPython source
|
|
|
tree. Or, when appropriate, a suitably named subdirectory should be used. The
|
|
|
documentation in this location will serve as the main source for IPython
|
|
|
documentation and all existing documentation should be converted to this
|
|
|
format.
|
|
|
|
|
|
In the future, the text files in the ``docs`` directory will be used to
|
|
|
generate all forms of documentation for IPython. This include documentation on
|
|
|
the IPython website as well as *pdf* documentation.
|
|
|
|
|
|
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
|
|
|
|
|
A bit of shell code:
|
|
|
|
|
|
.. sourcecode:: bash
|
|
|
|
|
|
cd /tmp
|
|
|
echo "My home directory is: $HOME"
|
|
|
ls
|
|
|
|
|
|
A bit of Python code:
|
|
|
|
|
|
.. sourcecode:: python
|
|
|
|
|
|
for i in range(10):
|
|
|
print i,
|
|
|
print "A big number:",2**34
|
|
|
|
|
|
An interactive Python session:
|
|
|
|
|
|
.. sourcecode:: python
|
|
|
|
|
|
>>> from IPython import genutils
|
|
|
>>> genutils.get_ipython_dir()
|
|
|
'/home/fperez/.ipython'
|
|
|
|
|
|
|
|
|
An IPython session:
|
|
|
|
|
|
.. sourcecode:: ipython
|
|
|
|
|
|
In [8]: import IPython
|
|
|
|
|
|
In [9]: print "This IPython is version:",IPython.__version__
|
|
|
This IPython is version: 0.9.1
|
|
|
|
|
|
|
|
|
|
|
|
Docstring format
|
|
|
----------------
|
|
|
|
|
|
Good docstrings are very important. All new code will use `Epydoc`_ for
|
|
|
generating API docs, so we will follow the `Epydoc`_ conventions. More
|
|
|
specifically, we will use `reStructuredText`_ for markup and formatting, since
|
|
|
it is understood by a wide variety of tools. This means that if in the future
|
|
|
we have any reason to change from `Epydoc`_ to something else, we'll have fewer
|
|
|
transition pains.
|
|
|
|
|
|
Details about using `reStructuredText`_ for docstrings can be found `here
|
|
|
<http://epydoc.sourceforge.net/manual-othermarkup.html>`_.
|
|
|
|
|
|
.. _Epydoc: http://epydoc.sourceforge.net/
|
|
|
|
|
|
Additional PEPs of interest regarding documentation of code:
|
|
|
|
|
|
- `Docstring Conventions <http://www.python.org/peps/pep-0257.html>`_
|
|
|
- `Docstring Processing System Framework <http://www.python.org/peps/pep-0256.html>`_
|
|
|
- `Docutils Design Specification <http://www.python.org/peps/pep-0258.html>`_
|
|
|
|
|
|
|
|
|
|
|
|
.. _devel_testing:
|
|
|
|
|
|
Testing system
|
|
|
==============
|
|
|
|
|
|
It is extremely important that all code contributed to IPython has tests. Tests
|
|
|
should be written as unittests, doctests or as entities that the `Nose`_
|
|
|
testing package will find. Regardless of how the tests are written, we will use
|
|
|
`Nose`_ for discovering and running the tests. `Nose`_ will be required to run
|
|
|
the IPython test suite, but will not be required to simply use IPython.
|
|
|
|
|
|
.. _Nose: http://code.google.com/p/python-nose/
|
|
|
|
|
|
Tests of `Twisted`__ using code should be written by subclassing the
|
|
|
``TestCase`` class that comes with ``twisted.trial.unittest``. When this is
|
|
|
done, `Nose`_ will be able to run the tests and the twisted reactor will be
|
|
|
handled correctly.
|
|
|
|
|
|
.. __: http://www.twistedmatrix.com
|
|
|
|
|
|
Each subpackage in IPython should have its own ``tests`` directory that
|
|
|
contains all of the tests for that subpackage. This allows each subpackage to
|
|
|
be self-contained. If a subpackage has any dependencies beyond the Python
|
|
|
standard library, the tests for that subpackage should be skipped if the
|
|
|
dependencies are not found. This is very important so users don't get tests
|
|
|
failing simply because they don't have dependencies.
|
|
|
|
|
|
We also need to look into use Noses ability to tag tests to allow a more
|
|
|
modular approach of running tests.
|
|
|
|
|
|
.. _devel_config:
|
|
|
|
|
|
Configuration system
|
|
|
====================
|
|
|
|
|
|
IPython uses `.ini`_ files for configuration purposes. This represents a huge
|
|
|
improvement over the configuration system used in IPython. IPython works with
|
|
|
these files using the `ConfigObj`_ package, which IPython includes as
|
|
|
``ipython1/external/configobj.py``.
|
|
|
|
|
|
Currently, we are using raw `ConfigObj`_ objects themselves. Each subpackage of
|
|
|
IPython should contain a ``config`` subdirectory that contains all of the
|
|
|
configuration information for the subpackage. To see how configuration
|
|
|
information is defined (along with defaults) see at the examples in
|
|
|
``ipython1/kernel/config`` and ``ipython1/core/config``. Likewise, to see how
|
|
|
the configuration information is used, see examples in
|
|
|
``ipython1/kernel/scripts/ipengine.py``.
|
|
|
|
|
|
Eventually, we will add a new layer on top of the raw `ConfigObj`_ objects. We
|
|
|
are calling this new layer, ``tconfig``, as it will use a `Traits`_-like
|
|
|
validation model. We won't actually use `Traits`_, but will implement
|
|
|
something similar in pure Python. But, even in this new system, we will still
|
|
|
use `ConfigObj`_ and `.ini`_ files underneath the hood. Talk to Fernando if you
|
|
|
are interested in working on this part of IPython. The current prototype of
|
|
|
``tconfig`` is located in the IPython sandbox.
|
|
|
|
|
|
.. _.ini: http://docs.python.org/lib/module-ConfigParser.html
|
|
|
.. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html
|
|
|
.. _Traits: http://code.enthought.com/traits/
|
|
|
|
|
|
|
|
|
Installation and testing scenarios
|
|
|
==================================
|
|
|
|
|
|
This section outlines the various scenarios that we need to test before we
|
|
|
release an IPython version. These scenarios represent different ways of
|
|
|
installing IPython and its dependencies.
|
|
|
|
|
|
Installation scenarios under Linux and OS X
|
|
|
-------------------------------------------
|
|
|
|
|
|
1. Install from tarball using ``python setup.py install``.
|
|
|
a. With only readline+nose dependencies installed.
|
|
|
b. With all dependencies installed (readline, zope.interface, Twisted,
|
|
|
foolscap, Sphinx, nose, pyOpenSSL).
|
|
|
|
|
|
2. Install using easy_install.
|
|
|
|
|
|
a. With only readline+nose dependencies installed.
|
|
|
i. Default dependencies: ``easy_install ipython-0.9.beta3-py2.5.egg``
|
|
|
ii. Optional dependency sets: ``easy_install -f ipython-0.9.beta3-py2.5.egg IPython[kernel,doc,test,security]``
|
|
|
|
|
|
b. With all dependencies already installed.
|
|
|
|
|
|
|
|
|
Installation scenarios under Win32
|
|
|
----------------------------------
|
|
|
|
|
|
1. Install everything from .exe installers
|
|
|
2. easy_install?
|
|
|
|
|
|
|
|
|
Tests to run for these scenarios
|
|
|
--------------------------------
|
|
|
|
|
|
1. Run the full test suite.
|
|
|
2. Start a controller and engines and try a few things by hand.
|
|
|
a. Using ipcluster.
|
|
|
b. Using ipcontroller/ipengine by hand.
|
|
|
|
|
|
3. Run a few of the parallel examples.
|
|
|
4. Try the kernel with and without security with and without PyOpenSSL
|
|
|
installed.
|
|
|
5. Beat on the IPython terminal a bunch.
|
|
|
6. Make sure that furl files are being put in proper locations.
|
|
|
|
|
|
|
|
|
Release checklist
|
|
|
=================
|
|
|
|
|
|
Most of the release process is automated by the :file:`release` script in the
|
|
|
:file:`tools` directory. This is just a handy reminder for the release manager.
|
|
|
|
|
|
#. Run the release script, which makes the tar.gz, eggs and Win32 .exe
|
|
|
installer. It posts them to the site and registers the release with PyPI.
|
|
|
|
|
|
#. Updating the website with announcements and links to the updated changes.txt
|
|
|
in html form. Remember to put a short note both on the news page of the site
|
|
|
and on launcphad.
|
|
|
|
|
|
#. Drafting a short release announcement with i) highlights and ii) a link to
|
|
|
the html changes.txt.
|
|
|
|
|
|
#. Make sure that the released version of the docs is live on the site.
|
|
|
|
|
|
#. Celebrate!
|
|
|
|