Preferred packages#
We have preferences for requirements in order to:
Limit the number of packages with which developers need to be familiar.
Re-use code (like Click) instead of writing new code (with argparse).
For applications, we prefer all-inclusive and opinionated packages, because they:
Promote greater similarity and code reuse across projects. Django encourages developers to use its authentication mechanism. With Flask, each developer can choose a different mechanism.
Are more robust to changes in scope. For example, you might not need the Django admin site on day one, but you’ll be happy to have it when it becomes a requirement.
Maintainers can find dependencies with:
find . \( -name 'setup.cfg' -or -name 'requirements.in' \) -exec echo {} \; -exec cat {} \;
Preferences#
- Web framework
Django LTS, unless a newer version has desirable features. Do not use Flask, except in limited circumstances like generating a static site with Frozen-Flask.
- API
Django REST Framework or FastAPI. Do not use Django Tastypie, which has fallen behind on Django and Python versions.
- Command-line interface
Click, unless a framework provides its own, like Django or Scrapy. Do not use argparse.
- Object Relational Mapper (ORM)
Django. If you don’t need an ORM, use psycopg2. Do not use SQLAlchemy, except in low-level libraries with limited scope where an ORM is needed.
Note
Use
psycopg2
in production, notpsycopg2-binary
, as recommended. See instructions.- HTTP client
Requests, unless a framework uses another, like Scrapy (Twisted).
- HTML parsing
lxml. Do not use BeautifulSoup.
- Markdown parsing
markdown-it-py. Do not use commonmark, which is deprecated.
- Templating
- Asset management
Do not use django-compressor or django-pipeline, which are always behind NPM packages. See Preferences for JavaScript.
- Translation
gettext and Babel, unless a framework provides an interface to these, like Django or Sphinx.
- Logging
- Testing
pytest, unless a framework uses another, like Django (unittest).
- Coverage
- Documentation
Sphinx. Its Markdown extensions should only be used for OCDS documentation.
Criteria#
A preferred package should meet the following criteria:
It is properly released: its readme is rendered on PyPI, a changelog is maintained, tags are used, etc.
It supports the most recent version of Python and frameworks (if relevant), like Django or Sphinx. Counter-example: django-tastypie (since resolved).
Simple libraries might not need new releases for new Python versions.
It meets the QASP criteria of published, tested and documented.
Published: Find its repository and check its open source license.
Tested: Check its CI badges, GitHub Actions tab, or CI configuration.
Documented: Check its documentation website.
Its issue tracker demonstrates that the maintainers are responsive. Counter-example: django-environ (since resolved).
The repository is not described as archived or unmaintained.
The maintainer’s other repositories can be considered if the repository is new or unpopular.
Snyk Open Source Advisor might also be used to answer the above.
License compliance#
To ease license compliance and code reuse, avoid software distributed under strong copyleft licenses.
Use an alternative dependency.
rfc3986-validator, not rfc3987
text-unidecode, not unidecode
Make the dependency optional.
try: import some_gpl_package using_some_gpl_package = True except ImportError: using_some_gpl_package = False if using_some_gpl_package: print("Some optional behavior")
Note
This does not apply to software that is only used as a utility and is not linked to the code, like libsass.
To list the licenses under which installed packages are distributed:
Install the packages
Install pip-licenses:
pip install pip-licenses
List the licenses:
pip-licenses --with-urls
If you have virtual environments for multiple repositories, you can do a bulk operation:
Install pip-licenses in all virtual environments. For example, if using pyenv-virtualenv (fish shell):
for env in (pyenv virtualenvs --skip-aliases --bare) pyenv activate $env pip install pip-licenses end
Initialize a CSV file as the output file:
echo Venv,Name,Version,License,URL > licenses.csv
Append licenses to the output file:
for env in (pyenv virtualenvs --skip-aliases --bare) pyenv activate $env pip-licenses --format=csv --with-urls | tail -n +2 | sed "s`^`$env,`" >> licenses.csv end
Run this script from the standard-maintenance-scripts repository:
./manage.py check-licenses licenses.csv