Python 3 and uWSGI on CentOS 7

Wherever I can, I avoid compiling applications from source or packaging up my own versions of things that exist in popular repositories. I recently needed to run a Python 3 application via uWSGI on CentOS 7 and was frustrated at how many search results were recommending building uWSGI from source when it’s available in EPEL. I’ve only deployed a handful of Python applications, and was not interested in keeping up with source-compiled dependencies to support this small project over time. We’ll discuss a couple of different ways to run these programs together with minimal customization.

Python 3.4

In CentOS 7, you can run Python 3.4 alongside Python 2.7 as it is available in EPEL.

rpm -q epel-release || yum -q -y install epel-release

yum -y install python34

pip

If you want to install pip, simply yum install python34-pip and it will install at /usr/bin/pip3 and /usr/bin/pip3.4, leaving /usr/bin/pip alone as that is part of the python2-pip package. This was only available recently (late 2016); previously you had to manually install and deal with /usr/bin/pip being overwritten.

Virtual Environments

Since 3.3, Python supports creating virtual environments without an external module like virtualenv. pyvenv was the “recommended tool” for this initially, though it is deprecated in Python 3.6. As such, to lean into the preferred Python nomenclature, you can use the venv module directly to create Python 3.4 virtual environments:

python3.4 -m venv /path/to/venv34dirname

source /path/to/venv34dirname/bin/activate

If you like using virtualenv, you’ll be disappointed to see that there is no python34-virtualenv package available in EPEL. While you could pip3 install virtualenv, that would install at /usr/bin/virtualenv, potentially overwriting the same file provided by the python-virtualenv package. But, if you do, consider the following:

# Install virtualenv with the pip provided the 'python34' package:
pip3 install virtualenv

# Then, a virtualenv based on /usr/bin/python3.4:
virtualenv /path/to/venv34dirname

# Now, to create based on /usr/bin/python2.7, using a newer version of virtualenv than is provided by the 'python-virtualenv' package:
virtualenv --python=/usr/bin/python2.7 /path/to/venv27dirname

# Or, recommended, use the alternate path from the 'python-virtualenv' package:
virtualenv-2.7 /path/to/venv27dirname

Unless you have a compelling reason for virtualenv on your servers, use the built-in venv module. I don’t like to overwrite files provided by packages, and we should use recommended upstream tools where possible.

uWSGI

Since uWSGI is provided by EPEL, there is no need to install it with pip.

yum -y install uwsgi uwsgi-plugin-python3

This will install the Python 3.4 uWSGI plugin to /usr/lib64/uwsgi/python3_plugin.so. Be sure to include the directive plugin = python3 in your uWSGI definition and proceed as normal (beyond the scope of this post).

Python 3.5 Software Collection

I love Software Collections. With Python 2.7 being the default version of Python with CentOS 7, I find it much better to keep another version in a completely separate directory structure than integrating with /usr and such. We’ll use a rebuild of the Red Hat SCL for Python 3.5, and everything will be installed to /opt/rh/rh-python35.

yum -y install centos-release-scl

yum -y install rh-python35-python

Using

You’ll quickly notice that the newly-installed Python 3.5 binary is not in your $PATH; it’s in /opt/rh/rh-python35/root/bin. You have a few options:

Launch a new instance of bash:

scl enable rh-python35 bash

Alternatively, you can modify your existing session by running one of the following manually, or add to ~/.bashrc:

# Preferred method:
source scl_source enable rh-python35

# Source the file directly:
#source /opt/rh/rh-python35/enable

pip

yum -y install rh-python35-python-pip

After installing, you can use pip, pip3, or pip3.5

Virtual Environments

You can use the built-in method:

python3.5 -m venv /path/to/venv35dirname

Or, you can use virtualenv (though, see the 3.4 section for the rationale I recommend against this):

yum -y install rh-python35-python-virtualenv

virtualenv-3.5 /path/to/venv35dirname

uWSGI

While there is a uwsgi-plugin-python3 RPM, it’s for the aforementioned python34 package. This is where you will have to compile something manually; so carefully consider the pros and cons of using a Software Collection. (Later, I may add to this post where you can use a yum post-install or post-upgrade action to ensure the compiled plugin is kept up to date.)

This is a helpful exercise to learn how you can integrate packages that install to default paths and a Software Collection which is kept away from default paths.

yum -y install uwsgi uwsgi-plugin-common

UWSGI_VERSION="$( rpm -q uwsgi --queryformat '%{VERSION}' )"
UWSGI_PLUGINS_DIR="$( dirname "$( rpm -q uwsgi-plugin-common -l | grep '_plugin\.so' | tail -1 )" )"

source scl_source enable rh-python35

if [[ $X_SCLS == *rh-python35* ]]; then
  UWSGI_PLUGINS_DIR="/opt/rh/rh-python35/root${UWSGI_PLUGINS_DIR:?}"
fi

if [[ ! -f "${UWSGI_PLUGINS_DIR:?}/python35_plugin.so" ]]; then
  yum -y install gcc libcap-devel libuuid-devel make openssl-devel rh-python35-python-devel

  mkdir -pv /opt/rh/rh-python35/root/src
  cd /opt/rh/rh-python35/root/src

  curl -LO "https://projects.unbit.it/downloads/uwsgi-${UWSGI_VERSION:?}.tar.gz"
  tar zxvf "uwsgi-${UWSGI_VERSION:?}.tar.gz"
  cd "uwsgi-${UWSGI_VERSION:?}/"

  make PROFILE=nolang
  PYTHON=python3.5 /usr/sbin/uwsgi --build-plugin "plugins/python python35"

  [[ ! -d "${UWSGI_PLUGINS_DIR:?}/" ]] && mkdir -pv "${UWSGI_PLUGINS_DIR:?}/"
  mv -v python35_plugin.so "${UWSGI_PLUGINS_DIR:?}/"
fi

(Reference: http://uwsgi-docs.readthedocs.io/en/latest/WSGIquickstart.html)

After completing, your resulting uWSGI file will need to contain:

plugins-dir = /opt/rh/rh-python35/root/usr/lib64/uwsgi
plugin = python35

Which should you use?

If you need Python 3 but want to reduce the amount of customization that you need to do, I recommend installing python34 from EPEL. You can use it with uWSGI with no compiling from source.

If Python 3.4 is too old and/or you need 3.5+ functionality, the Software Collection can be an alternative to compiling Python from source and knowing that you’re getting quality packages designed by Red Hat and rebuilt by CentOS.

If you need Python 3.6, or a newer release of 3.4 or 3.5 not provided quickly enough by the package maintainers, and you don’t want to deal with all of the pain of ./configure && make && sudo make install, I would suggest looking at pyenv to meet your needs.

Please let me know if I missed something or should add anything. I’m cumulatively green with running Python in production but wanted to share my learnings as I was frustrated by the lack of good information for my needs. Feedback is welcome.