Suppose you want to make a cython module for your python project. The tutorial is easy to follow but there is a major pain point which is glossed over. Let’s go through the example and see how to solve the problem that will pop out.

cython example

In our package we have two files, setup.py and helloworld.pyx. This is their content.

# setup.py
from setuptools import setuptools
from Cython.Build import cythonize

setup(
	ext_modules=cythonize("helloworld.pyx")
)
# helloworld.pyx
print("Hello World")

Now the instructions ask us to issue this command and most likely you will get this output because you have not yet installed cython as a package.

$ python setup.py build_ext --inplace
Traceback (most recent call last):
  File "/some/path/to/cython-example/setup.py", line 2, in <module>
    from Cython.Build import cythonize
ModuleNotFoundError: No module named 'Cython'

This is no way to reliably build your package and we can do better. The way forward is described in PEP-517 and PEP-518. Together these two PEPs describe a reliable way to have a python environment set up before you build your packages.

cython as a build dependency

Because cython is a build dependency for you package it should be available before you build the package . At the same time cython is not a runtime dependency: once you get C code out of cython you can distribute that. There is no reason to have your users install cython when there is an appropriate source package to use.

You can add a pyproject.toml file to your directory to document these build dependencies. Python build tools like pip and conda will pick it up automatically. In the build-system section of my pyproject.toml I added a requires key and specified that to build my package I need both of cython and wheel in addition to setuptools.

# pyproject.toml
[build-system]
requires = ["setuptools", "wheel", "cython"]

I’m adding cython because I want it to be available at build time so that the import from Cython.Build import cythonize does not fail like before. I’m also adding wheel because I want to be able to distribute binary packages in addition to source packages. This is extremely convenient for cython because not all environments have a compiler ready to tranform the output C code to a binary.