Development¶
pymmich is a small Python package managed with uv
and a top-level justfile.
Layout¶
.
├── justfile # cross-platform task runner
├── pyproject.toml # package metadata and dependencies
├── README.md
├── LICENSE.md
├── src/
│ └── pymmich/ # library + CLI source
├── tests/ # pytest suite
├── docs/
│ ├── zensical.yml # Zensical config
│ └── content/ # Markdown sources (logo, assets, commands/, …)
└── scripts/ # build helpers (pyinstaller, cleanup, ...)
Why just?¶
The project targets Linux, macOS, and Windows. A shell-driven
Makefile doesn't really work on Windows without extra tooling
(make.exe from MSYS, workarounds for find/rm/etc.). just
is a single prebuilt binary, has identical syntax on every OS, and is
shipped as the pip-installable rust-just wheel — so it becomes part
of the venv the moment you run uv sync.
Setting up a dev environment¶
uv sync --all-groups # creates .venv + installs every dep group,
# including `just` itself
After this .venv/bin/just (or .venv\Scripts\just.exe on Windows)
is available. You can either prefix every call with uv run:
uv run just <recipe>
or activate the venv and use just directly:
# POSIX
source .venv/bin/activate
just <recipe>
# Windows (PowerShell)
.venv\Scripts\Activate.ps1
just <recipe>
Running the test suite¶
uv run just test
# or with extra pytest args:
uv run just test -k list_users -v
Tests use respx to mock the Immich HTTP API — no live server is ever contacted, so the suite is hermetic and fast.
The mocked suite reports Immich 2.7.5 by default, so it exercises
the v2 API variant; individual tests switch to a 3.x server with the
set_server_version fixture. See
Server compatibility for what differs.
Running the live tests¶
The mocked suite pins down which requests pymmich sends. The live suite checks that real Immich servers accept them, and answer the way the client assumes. It runs every test twice — once against Immich 2.7 and once against Immich 3.1 — so a behaviour that differs between the two shows up as a failure instead of a silent divergence.
It needs Docker and is opt-in; uv run just test never touches it.
uv run just docker-test-up # start both servers and bootstrap them
uv run just docker-test # run the live tests (implies docker-test-up)
uv run just docker-test-status # show what is running
uv run just docker-test-down # stop everything and delete the data
just docker-test forwards extra arguments to pytest, e.g.
uv run just docker-test -k shared_album -v.
Two throwaway servers are started from the official
ghcr.io/immich-app/immich-server images, each with its own database
and cache, published on 127.0.0.1:2287 (2.7) and 127.0.0.1:2317
(3.1). A cold start takes about 20 seconds; the first run pulls
roughly 3 GB per version. Nothing is persisted — just docker-test-down
removes every trace.
See docker/README.md
for how the fixture is put together and why it must not be used as a
deployment template.
Disposable fixture servers, not real ones
Both servers are explicitly disposable: they use a well-known admin
password, run their database without crash safety, and keep nothing
across just docker-test-down. Every container is labelled
pymmich.disposable=true to say so, and every port is bound to
loopback so they are unreachable from the network — keep it that
way.
Building a distribution¶
uv run just build # wheel + sdist in ./dist/
uv run just pyinstaller # standalone one-file binary in ./dist/pyinstaller/
See the Distribution page for the details of each artifact and the per-platform build workflow.
Building / serving the docs¶
uv run just docs # static site in ./site/
uv run just docs-serve # live reload at http://127.0.0.1:8000
Cleaning up¶
uv run just clean # remove build, dist, site
uv run just distclean # clean + caches, __pycache__, editor backups
Adding a new CLI command¶
The CLI lives in src/pymmich/cli.py and is built on top of
Typer. The HTTP client with all the
Immich-specific knowledge lives in src/pymmich/client.py. Keep new
commands thin — business logic belongs in the client.
Write a test first (TDD is the project rule), then the implementation.
Adding a new just recipe¶
Edit justfile at the top of the project. The last comment line
before a recipe becomes its description in just --list — keep it
short and verb-first. Use Python (uv run python scripts/…) for
anything that would need different shell commands on Windows vs. POSIX
rather than writing two [unix] / [windows] variants.