Turbocharging Development with uvx

Boilerplate is not your friend

When someone creates a new Python project, often they are implementing the same boilerplate as thousands of other projects. Django is probably the best example of this. Understandably, the official Django docs provide its users a command to initialize a project:

$ django-admin startproject mysite djangotutorial

This command, presumes that you either have a virtual environment activated, or that Django is globally installed. Of course, as everyone hopefully knows nowadays, you should never create a project without using a virtual environment (unless you have good reason).

https://commons.wikimedia.org/wiki/File:Django_logo.svg
Python's biggest web framework

While Django does explain this somewhat on its installation guide, it still takes for granted that you need a system installation of Python. That means that anyone who is new to Python, and is following the documentation closely, will miss out on the benefits of uv. For more on uv and why it is important, read this article.

Perhaps people assume that packaging tools are the preoccupation of advanced users. If that is the case, people learning Python from scratch will have a needlessly convoluted introduction to packaging and project management, all because they were steered in the wrong direction.

Ultimately, there are numerous steps to create a Django project:

  1. Install Python if not already installed.
  2. Create a directory in which to create the project.
  3. Create a virtual Python environment.
  4. Activate the virtual environment.
  5. Install Django with pip.
  6. Intialize the Django project with the previously shown command.

Not only is it slow, it also means that someone new to Python has more possible points of failure. And while failure can be a good learning experience, it’s not worth it if we are teaching people outdated project management techniques. The truth is that we are able to shorten this process.

  1. Install uv if not already installed.
  2. Use uv to initialize a project.
  3. Install Django with uv.
  4. Intialize the Django project with the previously shown command.

That’s not bad. But it could be even better.

Trimming the fat

While Django is a good example, it is not the only Python web framework that involves a tedious process to simply create a project. Pelican has a similar process. For those who don’t know, Pelican is a static-site generator written in Python. Much like Django, it has its own special command to initialize a Pelican project (again, you will be needed to have a Python virtual environment made ready prior):

$ pelican-quickstart

When teaching someone to program, there are often competing goals of teaching something so simple that they understand it, while also teaching them something so useful that they benefit from using it in the future. I wanted the best of both worlds. The reason I first touched Pelican was because I wanted to show someone completely new to programming how to create a GitHub Pages static-site in just minutes, using a Python-based static site generator. Pelican is presently the most popular tool for the job.

For reasons that I hope readers can understand, using uv isn’t simply superior to a vanilla packaging experience. It’s easier. Teaching good packaging practices should frankly be taught as early as possible.

Of course, I could go through the following steps:

  1. Install uv if not already installed.
  2. Use uv to initialize a project.
  3. Install Pelican with uv.
  4. Intialize the Pelican project with the previously shown command.

But like I said, there’s an even easier way to do this, inspired by a packaging system for a completely different language. You see, for all the many, many flaws of Node.js, it is very easy to start a project by using the npx command. npx, which stands for “Node Package eXecuter”, can download and install an npm package, then run a command from that package. For example:

$ npx cowsay "Visit elliotsimpson.org!"
 __________________________
< Visit elliotsimpson.org! >
 --------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

There’s a little more to the command, but this is the core functionality that makes it so good. You don’t need to install cowsay before running it, and you don’t need to worry about the effect it will have on your system, because it gets cleaned up for you.

Besides making cows give dubious advice, npx is commonly recommended for the initialization of new projects. If you want to create a Next.js application, you can type npx create-next-app. If you want to create a React Router application, you can type npx create-vite. These tools can create your project folder, populate it with a template, and install all required dependencies, without any trouble.

Turbopelican

You can likely see where all this is going. Thus we have the uvx command. Of course, some users may already be familiar with pipx, which was very similar, but didn’t fit into the uv ecosystem. So if one installs uv, it becomes possible to do the following:

$ mkdir my-website
$ cd my-website
$ uvx --from pelican pelican-quickstart

It’s a little verbose, but it gets the job done. Or at least, some of the job. The pelican-quickstart command simply creates a new project via a template, but it doesn’t actually set up an environment. It rather leaves that to the user, especially because the way the docs are written, people will install Pelican into their virtual environment (and thus the project’s virtual environment becomes a prerequisite to run the command in the first place).

I wasn’t particularly happy with this. If create-next-app can be batteries-included, why can’t Pelican? Of course, it is unlikely that Pelican will implement functionality reliant on uv for the indefinite future, but we can take matters into our own hands.

And so I devised a plan. I would create my own Python package which could create a Pelican project from scratch, without the need for any existing virtual environment. This tool I would call Turbopelican. The package has the same name as its command (turbopelican of course), such that when I run uvx, I don’t need to specify a command separately from the package name – it correctly assumes that the turbopelican command is from the turbopelican package. This command copies a bunch of files into the target directory. Also, because I wanted in particular for the user to benefit from GitHub Pages, it copies across a workflow to make Pelican generate a static site and deploy it. Also, importantly, it uses uv to create a virtual environment for the project.

https://github.com/turbopelican/turbopelican/blob/main/assets/logo.svg
My sorry attempt at a logo for Turbopelican

If you have uv installed you can run it with the following command:

$ uvx turbopelican init path/to/projects/folder/my-website-name

Notice how what was once quite a long list of tasks has turned into the following:

  1. Install uv if not already installed.
  2. Run turbopelican.

By only supporting use of my tool with the uvx command, I also eliminate the scenario in which a user initializes a new project without having uv. Therefore, I can guarantee that the user receives a uv-managed virtual environment.

As many of you could likely see coming, feature creep attacked before I even put out a single release, and I soon wanted a more dynamic configuration loading system… but that is another story. The main thing is that the proof-of-concept worked, was very easy to implement, and was very, very easy to use.

Conclusion

Pelican is just one of the various significant frameworks that Python has to offer – there’s potential for other similar packages to streamline project creation, making use of the uvx command. Maybe soon we’ll have commands like uvx new-django-project or uvx pygame-init that create not only a template, but an entire project, complete with a virtual environment managed with uv.

Related
Python