Python development guide
This is a WORK IN PROGRESS. Help us make it better by submitting an issue or joining us in the 18F only, #python channel!
This document is structured by topic; under each, we include “Standards”, “Defaults”, and “Suggestions”.
Standards are practices that have a strong consensus across TTS; they should generally be followed to ease the ATO process and make on-boarding simpler.
Defaults are safe selections that tend to be used by a large number of our projects; you may find yourself with a better or more tailored solution, however.
Suggestions contain examples that have worked well on a project or two; they're not widely used enough to be defaults, but are worth considering.
Versions
We've standardized on Python 3.x over 2.x. All new projects should begin their life in 3.x, though legacy libraries can continue to support 2.x (in addition to 3.x) through tox. When starting a Python project, select the latest Python release available on cloud.gov and incrementally update as new releases are issued.
When using Django, we default to starting with the most recent Long Term Support release. This will give your project the most runway of support. If a second LTS becomes available while building the project, upgrade at the earliest convenience. Devs that follow will thank you.
Otherwise, our standard practice is to use the latest release of our libraries when first installing. Security updates (as indicated by GitHub or Snyk) should be applied ASAP, but all libs should be updated at some routine interval (e.g. quarterly).
Finally, in an effort to ensure our deployments are repeatable, our code standards require all dependencies (including dependencies' dependencies) be pinned to specific versions. This should also apply to the development environment (e.g. linters, testing tools, etc.) Suggestions for implementing that include
- poetry
- pip-tools's
pip-sync
- pipenv's
Pipfile.lock
- vendoring dependencies (though this is only a partial solution)
Style
Our standard tool for ensuring consistency across Python code bases is flake8. Its default settings are a good first step, as is using its integration with isort for import order. We suggest investigating flake8's plugin ecosystem for more functionality.
Use Black for automatic code formatting.
You are welcome to use Ruff instead as it combines all of the above packages into one and runs faster. It may become our standard in the future.
Using Code Climate to measure complexity scores (by way of radon) is also a reasonable default to ensure you see potentially confounding functions and classes.
Libraries
The Python ecosystem is large and full of alternative solutions to similar problems. Here we document a few common use cases and the libraries we recommend when trying to solve them.
Purpose | Library | Conviction |
---|---|---|
Test Runner | py.test | Standard |
Web framework | Django | Default |
ORM | Django | Default |
API | Django Rest Framework | Default |
HTTP Client | Requests | Default |
Task Queue | Celery | Suggestion |
Type support
Python 3.5 and beyond have had partial support for static type hints. Static typing can both make code authors' intent clearer and reduce the number of bugs through static analysis. It's also notorious for slowing down the pace of prototyping and requiring a great deal of boiler-plate.
Given this state, we believe it's reasonable to default to using type annotations when they make your intent clearer (i.e. as a form of documentation). We suggest using a static analysis tool (such as mypy) to catch logic bugs, but only where it's practical. Consider an allowlist of files to run against.