Improving our Python project management

With the Swedish summer upon us, things are more calm at Bitcraze. The summer is usually a time for us to look a bit more at fixing infrastructure and other things that we do not have time to work on the rest of the year. One of the things I have been looking at improving lately is the state of our Python projects.

We currently use Python as the default language for everything we do on PC. This includes the Crazyflie lib, Client and other tools for our ecosystem. Over the years the state of the Python projects have greatly evolved. It started in ~2011 with almost no project management at all; just Python files. Then we switched to setup.py/pip support. Recently the Crazyflie client got the pyproject.toml treatment. Now that most Linux distributions prevent pip install-ing packages we need to juggle with venvs in order to use or develop in Python.

In essence, Python started with an easy to use language but has now become quite complex and hard to handle. Things become even more complicated when we take into account CI in Github actions that have to test that our projects actually work with all supported version of Python on Linux/Mac/Windows.

A bit or Rust (tooling) in our Python

As you might have gathered from our previous blog posts over the years, we like Rust quite a bit at Bitcraze and hope to use it more in our products moving forward. One of the great parts of Rust is the quality of the tooling and of the compiler feedback. Cargo as a project management tool helps a lot working with projects in a comfortable and repeatable way.

This is why we are now quite interested in using uv going forward as an official tool to work with the Bitcraze Python projects. uv can replace both venv and pip and makes working with a Python project as easy as working on a Rust one with cargo. It is also very fast and efficient since it is written in … Rust of course :-).

We are also looking at switching from Flake8 to Ruff and Ty for linting and type checking respectively. These two tools, from the same developer as uv are very fast and give very high quality error messages and warnings – this should make it much easier to maintain good code quality.

These changes would mostly be on our documentation and development side. The resulting projects are still compatible with pip and can still be used as they where used before. However we would make sure the projects can be efficiently used with uv.

Example

The Crazyflie client is currently already fully able to run with uv since it already uses the ‘new’ pyproject.toml project config file. So working with the project, from within the project folder, would look like that:

crazyflie-clients-python $ uv run cfclient    # Run the Crazyflie client GUI
(...)

crazyflie-clients-python $ uv run ruff check  # Check the code, runs in ~100ms!
All checks passed!

crazyflie-clients-python $ uv run pre-commit
      Built cfclient @ file:///home/arnaud/dev/bitcraze/crazyflie-clients-python
Uninstalled 1 package in 0.97ms
░░░░░░░░░░░░░░░░░░░░ [0/1] Installing wheels...                                                                                              warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
Installed 1 package in 4ms
[WARNING] Unstaged files detected.
[INFO] Stashing unstaged files to /home/arnaud/.cache/pre-commit/patch1750684440-121836.
flake8...............................................(no files to check)Skipped
[INFO] Restored changes from /home/arnaud/.cache/pre-commit/patch1750684440-121836.Code language: Bash (bash)

This last command is a great example of the usefulness of uv: currently one need to install pre-commit on a virtual environment, enter it, and run pre-commit in the project. With uv is just works out of the box. In the back of course, pre-commit is installed in a virtual environment in the project folder. But this is all done automatically.

Finally we will also easily be able to test multiple versions of Python:

crazyflie-clients-python $ uv run --python 3.10 pre-commit
Using CPython 3.10.18 interpreter at: /home/linuxbrew/.linuxbrew/opt/python@3.10/bin/python3.10
Removed virtual environment at: .venv
Creating virtual environment at: .venv
      Built cfclient @ file:///(...)/crazyflie-clients-python
    Updated https://github.com/bitcraze/crazyflie-lib-python.git (3a35d22026c2ed8251b821e4f5b10e67091f811f)
      Built cflib @ git+https://github.com/bitcraze/crazyflie-lib-python.git@3a35d22026c2ed8251b821e4f5b10e67091f811f
░░░░░░░░░░░░░░░░░░░░ [0/32] Installing wheels...                                                                                             warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
Installed 32 packages in 699ms
[WARNING] Unstaged files detected.
[INFO] Stashing unstaged files to /home/arnaud/.cache/pre-commit/patch1750684632-123219.
[INFO] Installing environment for https://github.com/PyCQA/flake8.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
flake8...............................................(no files to check)Skipped
[INFO] Restored changes from /home/arnaud/.cache/pre-commit/patch1750684632-123219.Code language: JavaScript (javascript)

And, for the end user, uvx also simplify running the client:

$ uvx cfclient   # Pulls and run cfclient from Pypi
(...)

$ uvx --from cfclient cfloader
      Built cfclient
Installed 22 packages in 164ms

==============================
 CrazyLoader Flash Utility
==============================

 Usage: /(...)/.cache/uv/archive-v0/_OZwn5_zGeTE-qFoK_kEG/bin/cfloader [CRTP options] <action> [parameters]

The CRTP options are described above

Crazyload option:
   info                    : Print the info of the bootloader and quit.
                             Will let the target in bootloader mode
   reset                   : Reset the device in firmware mode
   flash <file> [targets]  : flash the <img> binary file from the first
                             possible  page in flash and reset to firmware
                             mode.Code language: HTML, XML (xml)

This last command would likely be added to all our firmware Makefiles to be used when calling make cload to flash the Crazyflie.

Feedback?

This currently does seem like a good idea to us. If you have any feedback or ideas on how to handle Python projects in a better way we are very interested to hear them. Like I mentioned, the summer is kind of a ‘clean up’ time for us so this is when we have time to look at this kind of things.

Leave a Reply

Your email address will not be published. Required fields are marked *