Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ jobs:
steps:
- uses: actions/checkout@v4 # shallow is fine: no test reads the setuptools-scm version

- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v5
with:
python-version: "3.11" # requires-python is >=3.11,<3.12
cache: pip
cache-dependency-path: pyproject.toml
enable-cache: true # cache uv's download/build cache across runs
cache-dependency-glob: uv.lock

- name: Install headless Qt system libs
# PySide6 6.5 needs these even under the offscreen platform. libglib2.0-0t64,
Expand All @@ -42,12 +41,13 @@ jobs:
sudo apt-get install -y --no-install-recommends \
libegl1 libgl1 libxkbcommon0 libfontconfig1 libdbus-1-3

- name: Install project + test deps
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[test]"
- name: Install project + test deps (frozen against uv.lock)
# --frozen: use the committed lock exactly, fail if it drifted from pyproject.toml.
# uv provisions Python 3.11 itself from requires-python; --no-default-groups skips
# the git-built `dev` group (funlib) the suite does not need; --extra test adds pytest.
run: uv sync --frozen --no-default-groups --extra test

- name: Run test suite (headless / offscreen)
env:
QT_QPA_PLATFORM: offscreen
run: python -m pytest -ra # -ra surfaces xfail/xpass/skip reasons if the gate goes red
run: uv run --frozen --no-default-groups --extra test python -m pytest -ra # -ra surfaces xfail/xpass/skip reasons if the gate goes red
54 changes: 38 additions & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,32 @@ privately as described in [`SECURITY.md`](SECURITY.md).
PyReconstruct targets **Python 3.11** (`requires-python = ">=3.11,<3.12"`) and
**PySide6 6.5.2**.

### Conda environment (current)
### uv (canonical)

The current developer workflow uses a conda environment named **`pyrecon_dev`**,
created by the `Makefile` in `dev/`:
The canonical developer setup uses [uv](https://docs.astral.sh/uv/). It reads the
Python 3.11 pin from `pyproject.toml`, provisions the interpreter, and installs
the exact dependency set recorded in the committed `uv.lock`:

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh # once; or: brew install uv
git clone https://github.com/dustenhubbard/PyReconstruct
cd PyReconstruct
uv sync # creates .venv from uv.lock (exact pinned deps)
uv run PyReconstruct # launch (installs PyReconstruct editable)
```

`uv sync` installs PyReconstruct in editable mode into `.venv/` (git-ignored), so
`import PyReconstruct` works with no `PYTHONPATH` fiddling. The update loop is
`git pull` then `uv run PyReconstruct` — `uv run` re-syncs `.venv` to the lockfile
automatically. To bump a dependency, edit its pin in `pyproject.toml` and run
`uv lock --upgrade` (or `uv lock --upgrade-package <name>`), then commit the
refreshed `uv.lock`. See [docs/DEV_UV.md](docs/DEV_UV.md) for the group/extra
matrix (runtime vs. `test` extra vs. `dev` group) and the full uv reference.

### Conda environment (alternative)

A conda environment named **`pyrecon_dev`** remains supported as a parallel
workflow, created by the `Makefile` in `dev/`:

```bash
cd dev
Expand All @@ -69,23 +91,17 @@ Other `Makefile` targets (run from `dev/`):

You can change the environment name by editing `ENV_NAME` in `dev/Makefile`.

> **uv migration (in progress).** A migration to [uv](https://docs.astral.sh/uv/)
> is underway: `pyproject.toml` already declares a PEP 735 `[dependency-groups]` dev
> group and a `[tool.uv.sources]` entry, intended to replace the conda-only dev
> extras. Until that lands, **the conda `make env` flow above is the canonical
> development setup.**

### Running the app from a checkout

In the activated environment, run the app from the repository root:
With uv, `uv run PyReconstruct` (above) launches the app. In an activated conda
environment, run it from the repository root instead:

```bash
python PyReconstruct/run.py
```

Alternatively, `pip install -e .` (or `uv pip install -e .` in a Python 3.11
`uv venv` — see [docs/DEV_UV.md](docs/DEV_UV.md)) installs the package in editable
mode and provides the `PyReconstruct` console command (the entry point declared in
Either way, an editable install (`uv sync`, or a plain `pip install -e .`)
provides the `PyReconstruct` console command (the entry point declared in
`pyproject.toml`, `PyReconstruct.cli:main`).

---
Expand All @@ -94,15 +110,21 @@ mode and provides the `PyReconstruct` console command (the entry point declared

The test suite lives in `tests/` and runs headless. It needs a Qt platform plugin
because a couple of tests construct a `QApplication`, so set the **offscreen**
platform. From the repository root:
platform. With uv (installs exactly what CI installs — runtime + the `test` extra):

```bash
QT_QPA_PLATFORM=offscreen uv run --no-default-groups --extra test python -m pytest
```

Or, in an activated conda environment, from the repository root:

```bash
QT_QPA_PLATFORM=offscreen PYTHONPATH="$PWD" python -m pytest
```

The suite is fast and requires no display or network. `pytest.ini` restricts
collection to `tests/` and runs quietly (`-q`). (In an environment created by
`make env`, the repo root is already on the import path, so `PYTHONPATH` is
collection to `tests/` and runs quietly (`-q`). (Under `uv sync` and in an
environment created by `make env`, the package is importable, so `PYTHONPATH` is
redundant there — but it's needed for a bare checkout.)

What the tests cover (a representative selection — the suite has grown well beyond
Expand Down
48 changes: 27 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,36 +65,42 @@ To track the latest commits on `main` (this replaces the old in-app "Developer"
update channel), run a source install rather than a frozen build.

PyReconstruct requires **Python 3.11** — the pinned version the app and its
native dependencies are validated on (the project pins `>=3.11,<3.12`). Your
system `python3` is likely newer, and `pip install -e .` will fail against it; the
steps below get you 3.11 without changing your system Python.
native dependencies are validated on (the project pins `>=3.11,<3.12`). The
canonical setup uses [uv](https://docs.astral.sh/uv/): it reads that pin, fetches
Python 3.11 for you, and installs the exact dependency set recorded in the
committed `uv.lock` — no system Python changes, no version guessing.

```
curl -LsSf https://astral.sh/uv/install.sh | sh # once; or: brew install uv
git clone https://github.com/dustenhubbard/PyReconstruct
cd PyReconstruct
uv sync # creates .venv from uv.lock (exact pinned deps)
uv run PyReconstruct # launch
```

# recommended: uv (installs Python 3.11 for you)
curl -LsSf https://astral.sh/uv/install.sh | sh # or: brew install uv
uv venv --python 3.11
source .venv/bin/activate # Windows: .venv\Scripts\activate
uv pip install -e . # editable: your working tree IS the running code
Update loop: `git pull`, then `uv run PyReconstruct` — `uv run` re-syncs `.venv`
to the lockfile automatically, so pulled code runs immediately with no manual
reinstall. You can also update from inside the app — **Help ▸ Check for updates**
on a source install reinstalls the branch set under **Series ▸ Options ▸ Updates**
(default `main`) — or from the command line with `PyReconstruct --update`
(`PyReconstruct --switch <branch>` to change branch first).

# alternative: plain venv (requires python3.11 already on PATH)
python3.11 -m venv .venv && source .venv/bin/activate
pip install -e .
<details>
<summary>Alternative: a plain <code>venv</code> without uv</summary>

If you already have `python3.11` on PATH, an editable install works too, though it
resolves dependencies fresh rather than from `uv.lock`:

PyReconstruct # launch
```
python3.11 -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
PyReconstruct
```

</details>

Pull to move to the newest code (`git pull`); because the install is editable,
the pulled source runs immediately with no reinstall — rerun the editable install
only when `pyproject.toml` changes. You can also update from
inside the app — **Help ▸ Check for updates** on a source install reinstalls the
branch set under **Series ▸ Options ▸ Updates** (default `main`) with `pip` — or
from the command line with `PyReconstruct --update` (`PyReconstruct --switch
<branch>` to change branch first). See [CONTRIBUTING.md](CONTRIBUTING.md) for the
full development setup (the conda `pyrecon_dev` environment, tests, and code
layout).
See [CONTRIBUTING.md](CONTRIBUTING.md) and [docs/DEV_UV.md](docs/DEV_UV.md) for the
full development setup (test suite, dev tooling, and code layout).

## Quickstart

Expand Down
37 changes: 24 additions & 13 deletions docs/DEV_UV.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# Developing PyReconstruct with uv

[uv](https://docs.astral.sh/uv/) is a fast, lockfile-based Python project manager.
This document describes the **uv-based development workflow**, which sits
*alongside* the conda workflow (`dev/environment_dev.yaml`) — both are supported,
pick whichever you prefer. uv has two advantages for contributors:
This document describes the **uv-based development workflow, which is the
canonical developer setup.** The conda workflow (`dev/environment_dev.yaml`)
remains supported as a parallel option, but `git clone` + `uv sync` + `uv run` is
the flow this project develops and releases against. uv has two advantages for
contributors:

- It provisions the correct interpreter itself (the project pins
`requires-python = ">=3.11,<3.12"`), so you don't need a separate conda env
just to get Python 3.11.
- `uv sync` installs PyReconstruct into a project-local `.venv` and resolves
against a committed `uv.lock`, so everyone gets the same dependency set — and,
because the package is installed, `import PyReconstruct` works with no
`PYTHONPATH` fiddling.
against the committed `uv.lock`, so everyone gets the same pinned dependency
set — and, because the package is installed, `import PyReconstruct` works with
no `PYTHONPATH` fiddling.

> `pyproject.toml` is the source of truth for the uv workflow. uv reads
> `[project.dependencies]` (runtime), `[project.optional-dependencies].test`
Expand Down Expand Up @@ -45,7 +47,8 @@ A machine that already runs the conda env (`pyrecon_dev`) has these already.
## 2. Create the environment — `uv sync`

`uv sync` creates `.venv/` in the repo (git-ignored), installs PyReconstruct in
editable mode, and writes/refreshes `uv.lock`. What *else* it installs depends on
editable mode, and applies the committed `uv.lock` (re-resolving only if
`pyproject.toml` has drifted from the lock). What *else* it installs depends on
which groups/extras you select:

| Want | Command |
Expand Down Expand Up @@ -127,16 +130,24 @@ display** — run it on macOS/Windows or a Linux box with a desktop, not offscre

## 6. The lockfile

`uv.lock` is generated on the first `uv sync` (or `uv lock`) and **should be
committed** — PyReconstruct ships as an application, so a pinned, reproducible
dependency set is what we want. To refresh it after changing pins in
`pyproject.toml`:
`uv.lock` **is committed** — PyReconstruct ships as an application, so a pinned,
reproducible dependency set is what we want. `uv sync` (and `uv run`) apply it as
is, and `uv sync --frozen` errors out rather than silently re-resolving if the
lock and `pyproject.toml` have diverged — that is what CI and reproducible setups
should use.

Bumping dependencies is a **maintainer** action: edit the pin in `pyproject.toml`
(or not, for a plain refresh), then re-resolve and commit the new lock:

```bash
uv lock # re-resolve and rewrite uv.lock
uv sync # apply it to .venv
uv lock --upgrade # re-resolve everything to newest allowed, rewrite uv.lock
uv lock --upgrade-package <name> # bump just one dependency
uv lock # re-resolve after editing a pin in pyproject.toml
uv sync # apply the new lock to .venv
```

Commit the resulting `uv.lock` alongside the `pyproject.toml` change.

## uv ↔ conda quick reference

| Task | conda (`pyrecon_dev`) | uv |
Expand Down
33 changes: 18 additions & 15 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,28 +110,31 @@ scikit-image, shapely, trimesh, zarr, and others). If you already have
.venv && source .venv/bin/activate`, then `pip install git+…`.

To **track the latest unreleased code on `main`** (what the retired in-app
"Developer" channel used to offer), clone the repository and install it editable,
then `git pull` whenever you want the newest commits:
"Developer" channel used to offer), clone the repository and let uv build the
environment from the committed `uv.lock`. This is the canonical developer setup:
`uv sync` reads the Python 3.11 pin, provisions the interpreter, and installs the
exact pinned dependency set.

```
git clone https://github.com/dustenhubbard/PyReconstruct
cd PyReconstruct
uv sync # creates .venv from uv.lock (exact pinned deps)
uv run PyReconstruct # launch
```

# recommended: uv (installs Python 3.11 for you)
uv venv --python 3.11
source .venv/bin/activate # Windows: .venv\Scripts\activate
uv pip install -e . # editable: the checkout IS the running code

# alternative: plain venv (requires python3.11 already on PATH)
python3.11 -m venv .venv && source .venv/bin/activate
pip install -e .
To move to the newest `main` later: `git pull`, then `uv run PyReconstruct` —
`uv run` re-syncs `.venv` to the lockfile automatically, so there is no manual
reinstall step.

git pull # later, to move to the newest main; rerun the
# editable install only when pyproject.toml changes
```
If you prefer a plain `venv` and already have `python3.11` on PATH, an editable
install works too, though it resolves dependencies fresh rather than from
`uv.lock`: `python3.11 -m venv .venv && source .venv/bin/activate`, then
`pip install -e .` (rerun only when `pyproject.toml` changes; a bare `git pull`
otherwise suffices).

For a full development setup (the conda `pyrecon_dev` environment, tests, code
layout), see [CONTRIBUTING.md](../CONTRIBUTING.md).
For a full development setup (test suite, dev tooling, the parallel conda
`pyrecon_dev` environment, code layout), see
[CONTRIBUTING.md](../CONTRIBUTING.md) and [DEV_UV.md](DEV_UV.md).

### Launching the app

Expand Down
Loading
Loading