2026-06-03 17:11:18 +02:00
2026-06-03 17:11:18 +02:00
2026-06-03 15:19:54 +02:00
2026-06-03 15:19:54 +02:00
2026-06-03 15:24:23 +02:00

Instrument Control for QPS Optics Lab

General rules

Please use initials and kebab case when opening a branch. For example if your name is John Doe, name your branch jd-branch-name.

Python Environments with uv — Lab Repo Guide (Written by Claude AI)


Installing uv

uv is a modern Python package and environment manager — much faster than pip and handles everything in one tool (environments, dependencies, Python versions). Install it with:

macOS / Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.sh | iex"

After installing, restart your terminal and verify it worked:

uv --version

If you have other Python installations on your machine

If you installed Python previously (via python.org, brew, conda, pyenv, etc.), those installations still exist and your terminal may default to one of them. This is fine — uv does not conflict with them. However, to avoid confusion:

  • Do not use pip install globally anymore. It installs packages into your system Python, which creates a mess over time. Use uv add inside a project instead (see below).
  • Check what Python your terminal defaults to at any time with which python or python --version. This tells you what runs when you type python without using uv.
  • uv manages its own Python versions independently. When you pin a project to Python 3.12, uv will download and use that version for the project regardless of what Python is installed system-wide. You don't need to uninstall anything.

If you want to see all Python versions uv knows about:

uv python list

Philosophy

Each project subfolder gets its own fully isolated environment — its own packages, its own Python version. Nothing bleeds between projects.

You always stay at the repo root for Git operations and use --project to point uv at the right subfolder. This way git push, git pull, and git status always work from one place.

INSTRUMENT-CONTROL/
├── .gitignore
├── README.md
├── your-project-folder/
│   ├── pyproject.toml      ← declares your dependencies (commit this)
│   ├── uv.lock             ← locks exact versions (commit this)
│   ├── .python-version     ← pins the Python version (commit this)
│   └── .venv/              ← the actual environment (gitignore this)
└── ptis-analysis/
    ├── pyproject.toml
    ├── uv.lock
    ├── .python-version
    └── .venv/

Which Python version to use

Use Python 3.12 for all new projects.

Python 3.9 reached end-of-life in October 2025 and no longer receives security patches. Python 3.12 is fully supported by the entire scientific stack (numpy, scipy, matplotlib, pyvisa, etc.) and has meaningful performance improvements. Avoid the absolute latest release (e.g. 3.14) as some libraries take a few months to catch up.

Pick one version and stick to it across all projects unless you have a specific reason not to — it makes debugging much simpler.


Setting up a new project

From the repo root, run:

uv init --python 3.12 your-project-folder

This creates the following inside your-project-folder/:

  • pyproject.toml — the project's metadata and dependency list
  • uv.lock — a locked snapshot of the full dependency tree (including sub-dependencies)
  • .python-version — a one-line file that just says 3.12

The .venv/ folder doesn't exist yet — it gets created the first time you add a package or run something.


Adding packages

uv --project your-project-folder add numpy matplotlib scipy

This does three things at once: updates pyproject.toml with the new dependencies, resolves the full dependency tree into uv.lock, and installs everything into .venv/. You never need to manually pip install anything.


Running a script

uv --project your-project-folder run python your-project-folder/measure.py

uv run automatically uses the project's environment — no need to activate anything first for one-off script runs.


Running Jupyter

First add Jupyter to the project:

uv --project your-project-folder add jupyter

Then activate the environment and launch:

source your-project-folder/.venv/bin/activate
jupyter lab

You need to activate here (rather than use uv run) because Jupyter is an interactive session, not a one-off command. After activating, your terminal prompt will change to show (.venv) — this means all python, jupyter, and other commands will use the project's environment until you run deactivate.

Create or open any .ipynb file inside your-project-folder/ and it will have access to all the project's packages. Before committing notebooks, clear the outputs (Kernel → Restart & Clear Output) to keep your Git diffs clean.


Checking the Python version

To confirm which Python a project is using:

uv --project your-project-folder run python --version

# or just read the pin file directly:
cat your-project-folder/.python-version

To change it:

uv --project your-project-folder python pin 3.12

The full daily workflow

# Pull latest changes
git pull

# Add a new package
uv --project your-project-folder add pyvisa

# Run your script
uv --project your-project-folder run python your-project-folder/measure.py

# Commit the updated dependency files
git add your-project-folder/pyproject.toml your-project-folder/uv.lock
git commit -m "add pyvisa dependency"
git push

Syncing after a clone or pull

The .venv/ folder is gitignored, so it doesn't exist on a fresh clone. uv sync recreates it from uv.lock:

uv --project your-project-folder sync

You need this in two situations:

  1. Fresh clone on a new machine.venv/ doesn't exist yet
  2. After a git pull where someone else added a dependency — your .venv/ is now out of date

If you're the one adding packages with uv add, you never need to sync — it handles installation automatically.

Mental model: uv.lock is the source of truth that lives in Git. .venv/ is a local build of it. uv sync = "rebuild .venv/ from uv.lock."


Why .venv/ is not committed

.venv/ contains compiled binaries and symlinks built specifically for your OS and CPU. If you pushed it and pulled it on a different machine, it simply wouldn't work. It's also hundreds of MB — Git isn't designed for that.

pyproject.toml and uv.lock together are everything needed to recreate the exact same environment anywhere:

File Commit? Why
pyproject.toml Yes Declares your dependencies
uv.lock Yes Locks exact versions — guarantees reproducibility
.python-version Yes Pins the Python version
.venv/ No Local build artifact — regenerated from the above

Quick reference

Task Command
Create new project uv init --python 3.12 <project>
Add a package uv --project <project> add <package>
Run a script uv --project <project> run python <script>
Activate interactively source <project>/.venv/bin/activate
Deactivate deactivate
Sync after clone/pull uv --project <project> sync
Check Python version uv --project <project> run python --version
Pin Python version uv --project <project> python pin 3.12
List available Pythons uv python list


.gitignore — Only read this if you are setting up a new repo

The .gitignore file tells Git which files to never track. It lives at the repo root and covers all subfolders. You only need to set this up once per repo.

Why you need it

Without a .gitignore, Git will try to track everything — including .venv/, which contains thousands of compiled files you never want in version control (see above). VS Code's source control badge will show thousands of "changes" that aren't really changes, making it impossible to see what's actually going on.

The .gitignore for this repo

Create a file called .gitignore at the repo root (next to your project folders, not inside them) with this content:

# uv environments
**/.venv/
.uv-cache/

# Python
__pycache__/
*.py[cod]
*.pyo
*.pyd

# Jupyter
.ipynb_checkpoints/
**/.ipynb_checkpoints/

# OS files
.DS_Store
Thumbs.db

# Editor
.vscode/settings.json
.idea/
*.swp
*.swo

# Measurement data (uncomment if needed)
# *.csv
# *.dat
# *.hdf5
# *.h5

The **/.venv/ pattern uses a wildcard so it catches .venv/ inside any subfolder automatically — you don't need to update it when you add a new project.

Committing the .gitignore

Yes, the .gitignore itself should be committed:

git add .gitignore
git commit -m "add .gitignore"

It's not sensitive or machine-specific — committing it means any collaborator or future clone automatically gets the same rules.

If Git is already tracking .venv/

If you set up the .gitignore after Git already started tracking .venv/, the file won't be automatically un-tracked — .gitignore only prevents new files from being added. To fix this:

git rm -r --cached your-project-folder/.venv/
git commit -m "untrack .venv"

The --cached flag removes it from Git tracking only — the actual folder stays on disk and your environment keeps working.

S
Description
Repo for general instrument control software in the QPS Optics Lab.
Readme
874 KiB