Structure of Python Projects¶
Want to contribute to a git-pull project? They follow a common layout. This guide can help you make sense of things!
tmuxp, libtmux, cihai, libvcs, vcspull, and others follow common patterns in their layout.
Styling¶
black is used for code formatting.
The max line length is 88 (compared to PEP8’s 79). flake8: noqa
is permitted for docstrings where
it’s too long.
Use $ make black
to run your code through black.
Use $ make flake8
to validate compliance with the project’s code standards.
Dependencies¶
Traditional requirements files¶
requirements/ - dependencies, for backwards support on systems not using pipenv
Not all projects may have these. If they don’t require third party dependencies in the main project, e.g. SQLAlchemy or colorama, then their may be no base.txt.
requirements/base.txt - Direct project dependencies. These things are included in the
install_requires
in setup.py, so they’re invoked via python setup.py install
.
requirements/test.txt - Test packages. Examples: pytest, pytest-rerunfailures. These can be
included via test_requires
in setup.py, so they’re invoked via python setup.py test
.
As of June 2018, our projects also use pyup.io for automated package updates. These don’t support Pipfile yet, so that’s another reason we’re not moving over to Pipfile immediately.
Pipfile¶
pipenv supports installing via a Pipfile:
$ pipenv install --dev --skip-lock
Our projects don’t use the Pipfile.lock since they can cause issues with dependencies / version constraints and has a performance penalty.
To keep a Pipfile up to date, you can do some combination of the following:
pipenv install --skip-lock --dev -r requirements/doc.txt && \
pipenv install --skip-lock --dev -r requirements/dev.txt && \
pipenv install --skip-lock --dev -r requirements/test.txt && \
pipenv install --skip-lock -r requirements/base.txt
Some projects may create a Make task for it in the Makefile:
sync_pipfile:
pipenv install --skip-lock --dev -r requirements/doc.txt && \
pipenv install --skip-lock --dev -r requirements/dev.txt && \
pipenv install --skip-lock --dev -r requirements/test.txt && \
pipenv install --skip-lock -r requirements/base.txt
Optional development tools¶
tmuxp¶
You can automatically launch and IDE-like terminal session from tmux via tmuxp and the includes .tmuxp.yaml file. These functionality will automatically handle dependencies via pipenv]
Make tasks¶
make(1) is a popular utility on POSIX-like systems to save common commands across codebases/checkouts. For convenience, these can be stored in tasks in Makefile to prevent unnecessary repetition.
To ensure uniformity and max interoperability across developer systems, Make tasks are used. In addition, makefiles make use of variables via command concatenation to find / filter source files across different platforms.
Makefile¶
Variable example:
PY_FILES= find . -type f -not -path '*/\.*' | grep -i '.*[.]py$$' 2> /dev/null
This is uses find(1) and grep(1) options tested to work across BSD, macOS,
Linux to scan files recursively (in nested directories), and also filter them. For instance,
-f -not -path '*/\.*
ignore files beginning with a dot, e.g. .env
, and grep’s -i '.*[.]py$$'
filters for only *.py
files. The double $$
is used to escape the $
. In regular expressions, a
$
is used to represent the end of a string.
doc/Makefile¶
Sphinx documentation tasks.
Variable example:
WATCH_FILES= find .. -type f -not -path '*/\.*' | grep -i '.*[.]rst\|CHANGES\|TODO\|.*conf\.py\|.*[.]py$$' 2> /dev/null
Will be used to generate a list of files to monitor for changes. This uses find ..
to look a
directory behind ./doc
(../doc
is the project root). It will monitor for *.rst
and *.py
files (python files have code documentation) and also for CHANGES
and TODO
(which include
reStructuredTest, but lack file extensions for legacy purposes.)
PYVERSION=$(shell python -c "import sys;v=sys.version_info[0];sys.stdout.write(str(v))")
Is used for version checks. It is a uniform and tested way to find the major python version (2
or
3
), since they used a different module to serve HTTP files:
WATCH_FILES= find .. -type f -not -path '*/\.*' | grep -i '.*[.]rst\|CHANGES\|TODO\|.*conf\.py\|.*[.]py$$' 2> /dev/null
PYVERSION=$(shell python -c "import sys;v=sys.version_info[0];sys.stdout.write(str(v))")
HTTP_PORT = 8031
serve:
@echo '=============================================================='
@echo
@echo 'docs server running at http://0.0.0.0:${HTTP_PORT}/_build/html'
@echo
@echo '=============================================================='
@if test ${PYVERSION} -eq 2; then $(MAKE) serve_py2; else make serve_py3; fi
serve_py2:
python -m SimpleHTTPServer ${HTTP_PORT}
serve_py3:
python -m http.server ${HTTP_PORT}
Task example: make watch
pytest¶
pytest is used for testing, instead of standard library’s unittest.
They reside in the project root, inside of the tests/ folder. Test files are kept in test_{subject_name}.py. In addition, helper modules of any name (e.g. helper.py) are permitted, in addition to the use of conftest.py (which is used by pytest’s fixture system)
setup.py¶
What you’ll find in a setup.py file.
requirements.txt integration¶
requirements.txt / requirements/base.txt for install_requires
requirements/test.txt for
install_requires
pytest integration¶
Overrides python setup.py test
with a custom class:
from setuptools import setup
from setuptools.command.test import test as TestCommand
class PyTest(TestCommand):
user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = []
def run_tests(self):
import pytest
errno = pytest.main(self.pytest_args)
sys.exit(errno)
setup(
# ... stuff
cmdclass={'test': PyTest},
)