Practical Habits for Productive Python Development

in #devyesterday

99.99% efficiency sounds great on paper. In practice you’re usually one better tool or one small habit away from not wasting an hour on installs, CI runs, or repetitive terminal typing. Here are three practical changes I use that save time every day.

1) Use uv instead of raw pip/venv

uv is a modern, Rust-powered package & project manager that combines virtualenv, pip, and pipx, and it’s really fast. On projects with many dependencies, you’ll see massive wins with it.

Here’s a quick benchmark takeaway: on one of my projects pip install -r requirements.txt took ~150s while uv pip install completed in ~1s. It’s needless to say you start projects quicker and your app builds are significantly faster.

Install uv

  • macOS / Linux (installer script):
curl -LsSf https://astral.sh/uv/install.sh | sh
  • Homebrew:
brew install uv
  • Windows (PowerShell installer / winget / scoop) — see official docs for options and latest instructions. ([Astral Docs][2])

Useful `uv` commands

# create venv (uses .venv by default)
uv venv

# install packages from pyproject or requirements via uv
uv pip install -r requirements.txt

# install only non-dev groups from pyproject
uv sync --no-dev

# add packages to groups
uv add --group dev --group test

I can then run a command like the one below to install just the main packages.

uv sync --no-dev

Or if I wanted specific (or all) groups, I could do that too.

uv add --group dev --group test

There are many more interesting ways to use uv, like running some commands faster, but that’ll be in a future post.

2) Bash aliases

Stop retyping long commands. You could put frequently used aliases in ~/.bash_aliases (or ~/.bashrc) and use them instead.

Practical examples I use

alias ptree='tree -I "__pycache__|*.pyc|.venv|.git|node_modules" --dirsfirst -L 3'
alias py='python'
alias pyma='python3 manage.py'
alias entervenv='source .venv/bin/activate'
alias dc='docker compose'
alias gcm='git commit -m'

3) Small scripts > manual workflows

Updating secrets in a running Swarm stack, for example, is a multi-step, error-prone process. Although it’s not something you’d frequently do, it’s all the same a tedious process. Wrap it in a script so you don’t do it wrong at 3 AM.

#!/bin/bash

# Usage: ./update-secret.sh secret-name secret-value [stack-name]

set -euo pipefail

SECRET_NAME=$1
SECRET_VALUE=$2
STACK_NAME=${3:-vmlc-prod}

if [ -z "$SECRET_NAME" ] || [ -z "$SECRET_VALUE" ]; then
    echo "Usage: $0 <secret_name> <secret_value> [stack_name]"
    exit 1
fi

TEMP_SECRET_NAME="${SECRET_NAME}-temp"

printf "%s" "$SECRET_VALUE" | docker secret create "$TEMP_SECRET_NAME" -

for service in db-migrate django celery_beat celery_worker; do
    docker service update \
      --secret-rm "$SECRET_NAME" \
      --secret-add "source=$TEMP_SECRET_NAME,target=$SECRET_NAME" \
      "${STACK_NAME}_${service}" > /dev/null
done

sleep 10
docker secret rm "$SECRET_NAME"
printf "%s" "$SECRET_VALUE" | docker secret create "$SECRET_NAME" -

for service in db-migrate django celery_beat celery_worker; do
    docker service update \
      --secret-rm "$TEMP_SECRET_NAME" \
      --secret-add "source=$SECRET_NAME,target=$SECRET_NAME" \
      "${STACK_NAME}_${service}" > /dev/null
done

sleep 10
docker secret rm "$TEMP_SECRET_NAME"
echo "Done! Secret $SECRET_NAME updated successfully."

If you copy this, adapt the `service` list to match your stack and test on staging first.

Bonus: prompt info at-a-glance

Add PS1 bits so your shell prompt shows whether you’re in a venv, the current git branch, and Docker context. There are various ways to customize it.

![]( align="center")

From the image above, you can see that I have (.venv) that shows up when I’m in a virtual environment, [release] to show the git branch I’m in, and {default} to indicate the Docker Context I’m in. They’re colored differently so I can quickly differentiate them. The $ is colored differently, too, purely just for style. You can do the same by editing the PS1 environment variable in ~/.bashrc. Here’s what mine looks like:

if [ "$color_prompt" = yes ]; then
    PS1='$(git rev-parse --abbrev-ref HEAD 2>/dev/null | sed "s/^/\[\033[31m\][/;s/$/]\[\033[00m\]/")$(docker context show 2>/dev/null | sed "s/^/\[\033[38;2;0;127;255m\]{/;s/$/}\[\033[00m\]/")\n\[\033[01;32m\]{\u}\[\033[00m\]\n\[\033[38;5;214m\]$ \[\033[00m\]'
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi

Links / further reading


Originally posted at blog.theolujay.dev