This repository provides a template for a pip-installable open-source Python package deployed using CircleCI to PyPI.
This is a cookiecutter template that can be used by temple to create and manage the project.
A new open source python package can be started with:
pip3 install temple temple setup [email protected]:CloverHealth/temple-python-public.git
Note It is recommended to create a private Python package first with temple and then convert it to an open source package.
When calling temple setup
, the user will be prompted for template parameters. These parameters
are defined in the cookiecutter.json file and are as follows:
repo_name
: The name of the repository and and name of the python package. The name has to use hyphens instead of underscores for spacing because of Gemfury issues (i.e.pip install my-installable-package
).module_name
: The name of the Python module that will be imported as a library. Modules must have underscores (i.e.import my_installable_package
)short_description
: A short description of the project. This will be added as the Github repo description and the description in the setup.cfg file. It will also be the description displayed when users dotemple ls
to list this project.deploy_branch
(default=master): The branch to which deployments will happen. By default, merging into master will trigger a deploy of a new package.
When using this template with temple setup [email protected]:CloverHealth/temple-python-public.git
, the hooks/pre_gen_project.py
and hooks/post_gen_project.py
files will be called to bootstrap your Python project.
Once this is complete, the user can take advantage of all of the scaffolding provided by the template,
which includes:
- Automatic deployment to PyPI when merging into the
deploy_branch
(seecircle.yaml
). - Pylint and flake8 integration (see
setup.cfg
for pylint and flake8 configuration). - Coverage integration (see
setup.cfg
for coverage configuration). - Automatic version tagging and version bumping using PBR.
- Automatic generation of ChangeLog and AUTHORS files using PBR.
- Automatic generation of a
__version__
string in theversion.py
file (imported in__init__.py
). - A makefile for setting up the development environment locally (
make setup
), running tests (make test
), and running validation (make validate
). - A CircleCI file for running tests, doing deployments, and verifying any project made with this template remains up to date.
- A
.gitignore
file with defaults for Python, Sublime, Vim, Emacs, Git, Pycharm, and Mac files. - Scaffolding for Sphinx documentation and automatic deployment of docs to ReadTheDocs.org.
By using this template for your Python project, you are implicitly engaging in a contract that you will keep your
project up to date with this template whenever it changes. You will know that your project is out of date
when CircleCI runs the temple update --check
command in the circle.yaml
file of the project. When this happens,
you can run temple update
locally in your project repo to pull in the latest updates.
Note If you must do a release because of emergency circumstances, comment out temple update --check
in the circle.yaml
file to temporarily bypass the template check.
It is important to keep any changes to the templated files of this project to a minimum, otherwise temple update
will produce diffs that can be difficult to merge. Along with that, minimally editing the templated files ensures
that your Python library project behaves similarly to all of the other ones at Clover. If there is an error in the
templated files or a change that needs to be propagated to every package (e.g. updating Python), then the change should
be made in this template repository.
Please be aware that editing any part of this template repository (even this README file) will cause CircleCI builds to fail for all packages built with this template. Any changes to the template should not be taken lightly, and ideally multiple changes are merged in at once.
There are quite a few interacting pieces of this template that are described in the following, along with a guide on how they work within the context of your Python package.
This template includes scaffolding for creating documentation with Sphinx, a tool for creating documentation for Python code. With Sphinx, one writes their documentation as ReStructured Text. The power of Sphix comes from its ability to handle directives to do special tasks with documentation, such as automatically documenting a module or running a piece of code and showing its output.
We used Sphinx and the Read the Docs Theme for building and styling documentation because of its ubiquity in the Python community. Along with that, we chose it because it makes documentation beautiful and searchable, something we hoped that would make writing documentation more fun for others.
For an example of a project at Clover that makes heavy use of Sphinx, check out the documentation folder for pgtest-pgsql.
Remember that one can also perform temple ls [email protected]:CloverHealth/temple-python-public.git
to see a list of
all projects spun up with this template for more examples.
Building docs also comes with this template. In order to build and look at docs locally, one has to first set up the
project with make setup
and then type make docs
to build docs. Docs can be opened with make open_docs
.
Note Docs are also built during make validate
in order to catch any documentation building errors during
continuous integration.
In order to add dependencies to your library, add them to requirements.txt
. Typically python packages will include
dependencies in setup.py
under the install_requires
attribute. This template uses a package
called PBR that modifies the setup.py
file and gets it to read
requirements from requirements.txt
.
While it makes sense to pin dependencies in an application, depedencies should never be pinned in the requirements.txt
of a Python library. There are two primary reasons for this:
- Assume you pin a library (e.g.
sqlalchemy
) to 1.1.1 in your library. If any application uses your library, it is also now forced to usesqlalchemy==1.1.1
. Requiring any other version ofsqlalchemy
by that application will either result in a dependency conflict or in an ambiguous version ofsqlalchemy
being used by the library and by the application depending on how deployment is orchestrated. - Even if one pins a library under a certain version like
sqlalchemy<1.3
, it can still cause issues. Say that a security patch was released and an application must now updatesqlalchemy
to 1.3. The problems from the first example will now arise, and then maintainers of the library need to edit its dependencies and deploy a new version before the application can be safely deployed.
The second option should only be used if you are certain that your library breaks under a particular version of a dependency.
Otherwise, one should also leave their dependencies unpinned or use >=
when specifying dependencies.
This template includes tests as part of the released library, meaning the application has the ability to install the package and run its tests againsts the requirements pinned by the application. This is the preffered way to catch issues with libraries and their dependencies.
Typically when deploying python packages, one will manually edit the version in a setup.py
file and then go through a
series of steps to tag the version and push it to a package server. This template takes care of all of those steps automatically.
Version management is performed by the PBR library. This is a library that is
used by setup.py
and has the following capabilities:
- Reads all
setup.py
settings from thesetup.cfg
file. - As detailed in the previous section, reads library requirements from a
requirements.txt
file. - Determines the package version by looking at the most recent tag and incrementing the version based on commit messages since the tag(more on this in a bit)
- Automatically generates an AUTHORS and a ChangeLog file.
PBR's version management eliminates the need for anyone to ever manually bump a version string, and it conforms to the Semantic Versioning Spec. In order to bump the version of a package, the user can make commit messages that start with the following in order to bump the version:
- Commits that start with
Sem-Ver: bugfix,
will bump thePATCH
number (see the Semantic Versioning Spec for info on this number and others that follow). - Commits that start with
Sem-Ver: feature,
orSem-Ver: deprecation,
will bump theMINOR
number. - Commits that start with
Sem-Ver: api-break,` will bump the ``MAJOR
number.
If one doesn't include any of these in their commit messages, the PATCH
number will be bumped. If one includes
multiple messages with Sem-Ver
tags, the one that bumps the version by the most will be used.
Note When using squash and merge with the Github API, your Sem-Ver
tags on your message should be placed in the
title of your commit message if you want versions to be bumped a certain way.
Deployment is performed with the deploy.py
script that is included in the template. The deploy script is executed
by CircleCI whenever the deploy_branch
is merged (master by default). The script does the following:
- Ensures proper environment variables are set and checks that we are on CircleCI
- Tags the repository with the new version
- Creates a standard distribution and a wheel
- Updates version.py to have the proper version
- Commits the ChangeLog, AUTHORS, and version.py file
- Pushes to PyPI
- Pushes the tags and newly committed files to Github
- ReadTheDocs will detect the change to the repo and build the latest docs
In order to pause deployment, either pause the CircleCI project or cancel the build after the deploy branch is merged.
Before doing any manual deployment commands, first type pip install -r deploy_requirements.txt
in your
project folder.
Manually deploying a package is not recommended. In the cases where it must happen, one can do the following locally in a shell:
PBR_VERSION=version.to.use python setup.py sdist bdist_wheel
The built packages under the dist
folder can then be manually uploaded to PyPI. If the manually-uploaded
package is an official release that is not temporary, the user should also tag their repo at the version of
the package.
Python libraries are set up to use pytest as the test runner and framework.
coverage is also used to ensure that code meets a minimum testing coverage
requirement. Testing is executed in the circle.yaml
file and can be executed locally with make test
.
By default, the template configures that every branch of code is covered by tests in the setup.cfg
file. It
is recommended to not turn off this setting and instead opt for placing # pragma: no cover
comments on
functions or lines of code that do not have any value in being covered by tests. By keeping this setting on,
it helps ensure that any new additions to the library have been tested or have at least been documented to say
that it isn't valuable to test.
For validation, flake8 and pylint
are used to do static analysis of code and perform style linting. These checks are executed in the circle.yaml
file and can be executed locally with make validate
.
Using this template ensures that your Python package behaves like all of the other Python packages at Clover, all the way from local development to documentation to production deployment. Having all of our Python packages set up, documented, and deployed in similar ways decreases the cognitive load for others using, fixing, and maintaining your tool.
Using this template also ensures your package is kept up to date with changes at Clover, such as when we upgrade Python to newer versions or potentially switch our deployment process.
Projects created with this template have Github configured to use squash and merge as the merging strategy.
The main reason for this is because we maintain an automated ChangeLog file of all of the changes for a particular
version. Squash and merge helps keep this file informative and keeps on random commits like lint
from
being displayed. The ChangeLog is automatically included in the documentation for a package. Go check out the
documentation for other packages to see this in action.