General documentation / cheat sheets for various languages and services

Best practices

Contents of .git/hooks/pre-push:

#!/bin/sh
printf '%s: running pytest\n' "$0"
pytest -q --no-cov

String formatting

Supply a variable precision as well as number in a string formatting:

from math import pi
precision = 2
# via interpolation
'%0.*f' % (precision, pi)
=> '3.14'
# via format
'{1:0.{0}f}'.format(precision, pi)
=> '3.14'
# with f-strings
f'{pi:0.{precision}f}'
=> '3.14'

References:

Packaging

As of v30.3.0 (released on 2016-12-08), setuptools allows basic package configuration fields to be specified in a text file, setup.cfg (inspired by distutils2)

Here’s a template to initialize setup.cfg:

[metadata]
name = helloworld
author = Christopher Brown
author_email = io@henrian.com
url = https://github.com/chbrown/helloworld
description = Greet world
keywords = hello, world
license = MIT
classifiers =
  Development Status :: 1 - Planning
  License :: OSI Approved :: MIT License
  Programming Language :: Python
long_description = file: README.md
long_description_content_type = text/markdown

[options]
zip_safe = False
packages = find:
python_requires = >=3.6
install_requires =
  cytoolz>=0.10.0
  numpy
setup_requires =
  pytest-runner
  setuptools-scm
tests_require =
  pytest
  pytest-black
  pytest-cov

[options.entry_points]
console_scripts =
  helloworld = helloworld.__main__:main

[aliases]
test = pytest

[tool:pytest]
addopts =
  --black
  --cov=helloworld
  --cov-branch

All the information is now in setup.cfg, but you’ll still need a setup.py stub:

from setuptools import setup

setup(use_scm_version=True)

Expose the installed version in helloworld/__init__.py:

__version__ = None

try:
    import pkg_resources

    __version__ = pkg_resources.get_distribution("helloworld").version
except Exception:
    pass

References:

Local development

The easiest way to link a package into Python’s default site-packages is via pip:

From a directory containing a setup.py:

pip install -e .

Which mostly just calls:

python setup.py develop

If you don’t have an proper package set up, there are a couple ways to manually link in specific directories:

  1. Permanently, via a *.pth file:
    # from a directory containing Python files
    SITE_PACKAGES=$(python -c 'import os,site;print(os.path.realpath(next(iter(site.getsitepackages()))))')
    pwd > "$SITE_PACKAGES"/my-python-modules.pth
    

    That python call gets the canonical/default/first site-packages path for your active python. The site.getsitepackages() call returns a list of full paths to your active site-packages directories, but we only want the first one, and they can be unwieldy; calling os.path.realpath cleans up the relative paths and resolves symlinks.

  2. Via environment variable:
    export PYTHONPATH=~/path/to/Python/files
    

Versioning

Bump the version in setup.py manually and commit the change. Then use the following to tag that commit and push to remote:

git tag v$(python setup.py --version)
git push --tags

Publishing to PyPI

These instructions are current as of 2019-03-31, and reflect the instructions from https://packaging.python.org/tutorials/packaging-projects/.

Definitely the first time you do this, and probably every so often besides, you’ll want to install / upgrade twine and wheel:

pip install -U twine wheel

Now we can build the source distribution and a universal (Python 2/3) “wheel”.

python setup.py sdist bdist_wheel

Ensure that ~/.pypirc includes your PyPI credentials. Then, finally:

twine upload dist/*

Clean up:

python setup.py clean --all
rm -rf dist/

Or just ignore the build clutter:

printf '%s\n' build/ dist/ >> .git/info/exclude

Dependency management

Install pip if you don’t have it:

curl -sO https://bootstrap.pypa.io/get-pip.py
python get-pip.py

Upgrade pip if you don’t have the latest:

pip install --upgrade pip

pip does a lot but doesn’t provide much inspection/maintenance tooling. Luckily, there are some handy packages that fill in some of pip’s gaps.

IPython / Jupyter interactivity

As of IPython 6.2, you can automatically print the result of the last assignment, as if it were an expression.

The default behavior:

[chbrown@air ~]$ ipython
Python 3.6.5 (default, Mar 30 2018, 06:42:10)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: pi = 3

In [2]: 

With the new “AST Node Interactivity” option:

[chbrown@air ~]$ ipython --InteractiveShell.ast_node_interactivity=last_expr_or_assign
Python 3.6.5 (default, Mar 30 2018, 06:42:10)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: pi = 3
Out[1]: 3

In [2]: 

In both IPython and Jupyter, this can be activated in a live session with the magic:

%config InteractiveShell.ast_node_interactivity = 'last_expr_or_assign'

This same config approach can be specified in the IPython default profile to apply to all sessions of both IPython and Jupyter; simply add the following to your ~/.ipython/profile_default/ipython_config.py file:

c.InteractiveShell.ast_node_interactivity = 'last_expr_or_assign'

Jupyter keyboard shortcuts:

Key(s) Action
ctrl + shift + - split cell at cursor (all contents before the cursor will be moved to a new cell above the current one)