Skip to content

Latest commit

 

History

History
219 lines (167 loc) · 9.5 KB

developing.md

File metadata and controls

219 lines (167 loc) · 9.5 KB

Developing

A starting point for contributing to LabelJetty - getting set up, the project layout, and how to test, both automatically and against a real printer. Pull requests are welcome, especially reports and fixes for printers other than the reference Vretti 420B.

Get set up

You need Python 3.11+ and uv. From the repo root:

# Install uv if you don't have it
curl -fsSL https://astral.sh/uv/install.sh | sh

# Install the project + all dependencies (editable), including the dev/test group
uv sync --group dev

uv run ... always uses the project's virtualenv, so you never activate it manually. Text/markdown rendering needs the DejaVu Sans font - present on most Linux desktops; on a bare box: sudo apt-get install -y fonts-dejavu-core.

You do not need a printer, a .env, or network access to develop and run the test suite.

Project layout

The code is an installable package, labeljetty, under src/. Its structure mirrors the layered architecture:

src/labeljetty/
├── config.py            # one Config (pydantic-settings) from .env / env vars
├── app.py               # wires everything together; the `labeljetty` entry point
├── version.py           # runtime version resolution (see BUILD.md)
├── testbench.py         # the manual hardware CLI (`labeljetty-testbench`)
├── printer/             # connection + library layer - knows TSPL, zero internal deps
│   ├── connection.py    #   raw USB I/O + device discovery
│   ├── tspl.py          #   TSPLPrinter: render → TSPL command streams
│   └── render.py        #   bitmap rendering (pdf/png/text/markdown/barcode/qr)
├── core/                # persistence: SQLModel models, engine, logging
│   ├── db.py            #   PrintJob / WorkerStatus models
│   └── sqltypes.py      #   custom SqlJsonText type
├── service/             # the background print worker
│   └── worker.py
├── integrations/        # optional integrations
│   └── homebox.py       #   Homebox client (v0.26 entities + labelmaker)
└── web/                 # interface layer
    ├── app.py, api.py   #   FastAPI app + REST API
    ├── ui.py            #   web-UI routes (Jinja2 + HTMX)
    ├── auth.py          #   require_access seam + providers
    ├── password.py      #   pbkdf2 hashing + labeljetty-hash-password CLI
    ├── templates/       #   Jinja2 templates (no build step)
    └── static/          #   htmx + CSS

Console entry points (defined in pyproject.toml): labeljetty (the service), labeljetty-testbench (the hardware CLI), labeljetty-hash-password.

Imports are package-qualified: from labeljetty.printer import TSPLPrinter.

Read the Design doc first - it explains the layers and the decisions behind them, which makes the code far easier to navigate.

Running from source

uv run labeljetty

This starts the REST API, the web UI, and the background worker on http://localhost:8888/. Copy sample.env to .env and set at least PRINTER_USB first - see the Setup guide.

Once it's running, the live API docs are the fastest way to explore and exercise the endpoints while developing: Swagger UI at /docs, ReDoc at /redoc, and the raw schema at /openapi.json (all auto-generated by FastAPI). See also the REST API reference.

SQLITE_PATH and IMAGE_STORAGE_DIRECTORY resolve relative to the current working directory, so run from the repo root (or use absolute paths in .env).

Testing

There are two complementary layers: a fully automated suite for everything, and the manual testbench for the one thing we never automate - real printing.

The automated suite (tests/) is hardware-free: it covers the TSPL command builder, rendering, persistence, the print-service worker, the Homebox client, and every REST + web-UI endpoint. It needs no .env, printer, or network - tests/conftest.py redirects config at a throwaway temp directory and fakes the USB/Homebox/worker boundaries before any labeljetty import.

# One-time: install the project + test tooling
uv sync --group dev

# Run the whole suite
uv run python -m pytest

# A single file / test
uv run python -m pytest tests/test_api.py
uv run python -m pytest tests/test_api.py::test_print_text_enqueues

# Keyword filter, quiet, last-failed-first
uv run python -m pytest -k homebox
uv run python -m pytest -q --lf

# With coverage
uv run python -m pytest --cov=labeljetty --cov-report=term-missing

CI (.github/workflows/tests.yml) runs the same suite on every push and PR against Python 3.11 and 3.12, with no secrets or services required.

Deep reference: isolation design, the full fixture list, what's covered (and what's deliberately not), and how to write new tests are all in Testing.

Real-world print tests with the testbench

src/labeljetty/testbench.py (the labeljetty-testbench command) drives the TSPLPrinter library directly - either against the real USB printer or in --dry-run mode, where the generated TSPL is printed to stdout instead of being sent to the device. It's how you check label positioning/sizing on real hardware, which is intentionally never automated.

Run everything from the repository root.

Dry-run (no hardware needed)

# Alignment/ruler test pattern as TSPL to stdout
uv run labeljetty-testbench --dry-run pattern

# The other renderers
uv run labeljetty-testbench --dry-run text "Hello world"
uv run labeljetty-testbench --dry-run markdown "# Title
* one
* two"
uv run labeljetty-testbench --dry-run barcode 12345678 --text "Item 42"
uv run labeljetty-testbench --dry-run qrcode "https://example.com" --text "box 1"
uv run labeljetty-testbench --dry-run pdf /path/to/file.pdf --page 0

To eyeball the actual rendered bitmap, monkeypatch printer._render_and_print_image to capture the PIL image and save it as a PNG.

Global options apply to every subcommand:

Option Default Meaning
--dry-run off Print TSPL to stdout instead of sending to the printer
--width-mm from env Label width in mm (DEFAULT_LABEL_WIDTH_MM, else 100)
--height-mm from env Label height in mm (DEFAULT_LABEL_HEIGHT_MM, else 30)
--dpi from env Printer resolution (DEFAULT_DPI, else 203)
--usb from env Override the PRINTER_USB selector (ignored in dry-run)

Per-subcommand options include --font-size / --fit (text & markdown; see auto-fit), --page (pdf), --type/--text (barcode), and --ecc/--text (qrcode). Run uv run labeljetty-testbench --help (or ... <command> --help) for the full list.

Against the real printer

Drop --dry-run to send to hardware (requires the printer setup - udev rule + PRINTER_USB):

# Print the alignment pattern on a 57x32 mm label and verify it lands flush to the edges
uv run labeljetty-testbench --width-mm 57 --height-mm 32 pattern

# Query live printer status (head open / paper out / ribbon out / ...)
uv run labeljetty-testbench status

# Diagnose whether this printer answers status queries at all
uv run labeljetty-testbench probe

# Send arbitrary bytes for debugging
uv run labeljetty-testbench raw '1b213f'          # hex
uv run labeljetty-testbench raw '~!T' --text      # literal text

The pattern command is the quickest way to validate geometry: a correctly configured label shows the border flush to all four edges, with the millimetre ruler ticks landing on whole millimetres. If status says "not available", your printer is write-only for status (common on cheap clones) - printing still works; probe confirms it.

Testing a new printer? This is the path. Run pattern to confirm geometry, probe to see whether status reads work, then the per-type renderers. Please report what you find in an issue or PR - that's how the supported-hardware list grows.

Smoke-test the running service

BASE=http://127.0.0.1:8888/api   # match your configured host/port

curl -s $BASE/worker/status                          # worker health
curl -s -X POST $BASE/print/png -F file=@tests/fixtures/label_test.png
curl -s "$BASE/jobs?limit=10"                        # inspect the queue
curl -s $BASE/printer/status                          # {reachable, status_supported, status}

Conventions

  • Match the surrounding code - naming, comment density, and idiom.
  • New tests go in tests/, named test_*.py; they inherit the isolation harness automatically. Anything that would print, hit USB, or call Homebox over the network must be faked - see Testing → Writing new tests.
  • The version is derived from git tags by hatch-vcs; there's no version string to bump. Releases and the Docker build are covered in Build & release.
  • Hardware-only tests (if you add any) are marked @pytest.mark.hardware and deselected by default.