Structure of Python ProjectsLink to section
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.
StylingLink to section
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.
DependenciesLink to section
Traditional requirements filesLink to section
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.
PipfileLink to section
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 toolsLink to section
tmuxpLink to section
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 tasksLink to section
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.
MakefileLink to section
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/MakefileLink to section
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/nullPYVERSION=$(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
pytestLink to section
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.pyLink to section
What you’ll find in a setup.py file.
requirements.txt integrationLink to section
requirements.txt / requirements/base.txt for install_requires
requirements/test.txt for
install_requires
pytest integrationLink to section
Overrides python setup.py test
with a custom class:
from setuptools import setupfrom 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},)