Commit initial

This commit is contained in:
2026-04-15 17:57:12 +02:00
parent 005d8e797e
commit 55516ee10f
269 changed files with 26854 additions and 0 deletions
@@ -0,0 +1,166 @@
---
phase: 03-printer-configuration
plan: "01"
subsystem: api
tags: [fastapi, peewee, htmx, alpinejs, jinja2, sqlite, forms]
requires:
- phase: 02-driver-management
provides: Driver ORM model, HTMX partial rendering pattern, error response pattern, test fixtures with tmp_data_dir
provides:
- POST /printers endpoint with form parsing, validation, checkbox-to-bool conversion, FK resolution
- DELETE /printers/{id} endpoint
- POST /clients endpoint with duplicate-name handling
- GET /printers page grouped by client with LEFT OUTER JOIN (no N+1)
- GET /clients page with creation form
- Alpine.js port auto-derivation (IP -> port name, preserves manual edits)
- HTMX-powered form submission with outerHTML swap on #printer-list and #client-list
- Integration test suite covering PRNT-01 through PRNT-09
affects:
- 03-02 (next plan in printer configuration phase)
- Any phase using Printer or Client ORM models
- Test isolation pattern now fixed in conftest.py (affects all future test suites)
tech-stack:
added: []
patterns:
- "Printer/Client CRUD via FastAPI Form() parameters with sync def handlers"
- "Checkbox boolean convention: 'on'=True, absent/empty=False"
- "Grouped list via defaultdict + LEFT_OUTER JOIN — no N+1 queries"
- "HTMX partial swap: success returns partial, failure returns error div with same id"
- "Alpine.js x-data for reactive port derivation with portEdited guard"
- "Peewee test isolation: conftest.py fixture teardown closes test-thread DB connection"
key-files:
created:
- imptune/api/printers.py
- imptune/api/clients.py
- imptune/templates/printers.html
- imptune/templates/clients.html
- imptune/templates/partials/printer_form.html
- imptune/templates/partials/printer_list.html
- imptune/templates/partials/client_list.html
- tests/test_printer_crud.py
modified:
- imptune/api/pages.py
- imptune/main.py
- imptune/db/database.py
- tests/conftest.py
key-decisions:
- "Use list(Printer.select().where(...)) in tests instead of Printer.get() — Peewee's get() uses paginate+cursor caching that fails across DB re-inits in the same process"
- "Close test-thread DB connection in conftest.py fixture teardown — thread-local Peewee connections persist across tests and read from stale DB"
- "Close db in lifespan shutdown — enables clean re-init when TestClient is restarted in the same process"
- "Alpine.js portEdited guard prevents port overwrite after manual edit (PRNT-03 requirement)"
patterns-established:
- "HTMX error fragment: <div id='printer-list' class='error'><p>{msg}</p></div> with matching id for outerHTML swap"
- "Grouped list query: LEFT_OUTER JOIN with defaultdict grouping, 'Unassigned' fallback for null FK"
- "Form checkbox handling: Form('') default, 'on' == True conversion"
requirements-completed:
- PRNT-01
- PRNT-02
- PRNT-03
- PRNT-04
- PRNT-05
- PRNT-06
- PRNT-07
- PRNT-08
- PRNT-09
duration: 7min
completed: "2026-04-10"
---
# Phase 03 Plan 01: Printer and Client CRUD Summary
**FastAPI printer CRUD with Alpine.js IP-to-port derivation, HTMX form submission, LEFT JOIN grouped list by client, and 10-test integration suite covering PRNT-01 through PRNT-09**
## Performance
- **Duration:** ~7 min
- **Started:** 2026-04-10T10:49:28Z
- **Completed:** 2026-04-10T10:56:22Z
- **Tasks:** 2 of 3 (Task 3 is checkpoint:human-verify — pending)
- **Files modified:** 12
## Accomplishments
- Printer CRUD: POST /printers (all 9 fields, checkbox bool conversion, optional FK), DELETE /printers/{id}
- Client CRUD: POST /clients (duplicate handling), GET /clients page
- Alpine.js port auto-derivation: fills `IP_x_x_x_x` from IP, preserves manual edits via `portEdited` guard
- Grouped list: LEFT_OUTER JOIN query, defaultdict grouping with "Unassigned" fallback, no N+1
- 10 integration tests pass (GREEN), full 58-test suite passes
## Task Commits
Each task was committed atomically:
1. **Task 1: Failing integration tests (RED)** - `9bc26e3` (test)
2. **Task 2: Full CRUD implementation + GREEN tests** - `356c2ee` (feat)
3. **Task 3: Browser verification** - pending (checkpoint:human-verify)
## Files Created/Modified
- `imptune/api/printers.py` - POST /printers, DELETE /printers/{id}, _render_printer_list helper
- `imptune/api/clients.py` - POST /clients, _render_client_list helper
- `imptune/api/pages.py` - Added GET /printers and GET /clients page routes
- `imptune/main.py` - Registered printers + clients routers; db.close() in lifespan shutdown
- `imptune/db/database.py` - Close existing connection before re-init in init_db()
- `imptune/templates/printers.html` - Printer page (form + list sections)
- `imptune/templates/clients.html` - Clients page (add form + list)
- `imptune/templates/partials/printer_form.html` - All 9 fields, Alpine.js x-data reactivity
- `imptune/templates/partials/printer_list.html` - Grouped by client with h3 headers, delete buttons
- `imptune/templates/partials/client_list.html` - Client table partial
- `tests/conftest.py` - Added db.close() teardown in tmp_data_dir fixture
- `tests/test_printer_crud.py` - 10 integration tests for all PRNT requirements
## Decisions Made
- Use `list(Model.select().where(...))` in tests instead of `Model.get()` — Peewee's `get()` uses `paginate(1,1)` with cursor caching that hits the wrong database when the deferred db is re-initialized between tests in the same process.
- Close db connection in conftest.py fixture teardown — thread-local Peewee connections persist across tests and read from stale DB path even after `db.init()` updates the path.
- Alpine.js `portEdited` boolean guard preserves manually edited port names when user changes IP (PRNT-03).
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Peewee thread-local DB connection leaks across test boundaries**
- **Found during:** Task 2 (GREEN phase verification)
- **Issue:** After TestClient exits and a new test begins with a fresh tmp DB, the test thread's Peewee connection still pointed at the previous test's DB file. `Printer.get()` would query the wrong database (empty or stale data).
- **Fix:**
1. Added `db.close()` in lifespan shutdown (main.py) so each TestClient teardown closes the ASGI-thread connection.
2. Added `if not db.is_closed(): db.close()` before `db.init()` in `init_db()` (database.py) so re-init always starts fresh.
3. Added db connection teardown in `conftest.py` `tmp_data_dir` fixture to close the test-thread's connection after each test.
4. Updated test DB queries from `Model.get()` to `list(Model.select().where(...))` to avoid Peewee paginate cursor caching issue.
- **Files modified:** imptune/main.py, imptune/db/database.py, tests/conftest.py, tests/test_printer_crud.py
- **Verification:** All 58 tests pass including cross-test ordering
- **Committed in:** `356c2ee` (Task 2 commit)
---
**Total deviations:** 1 auto-fixed (Rule 1 - Bug)
**Impact on plan:** Fix was necessary for test correctness. The underlying isolation pattern now benefits all future test suites in this project. No scope creep.
## Issues Encountered
- Peewee `Model.get()` uses `paginate(1,1)` which clears `_cursor_wrapper` cache and re-executes — but after db re-init, the cursor wrapper was returning empty even though `count()` and direct SQL showed the record existed. Root cause: thread-local SQLite connection not updated by `db.init()`. Resolved by proper connection lifecycle management.
## User Setup Required
None — no external service configuration required.
## Next Phase Readiness
- /printers and /clients pages functional with full CRUD
- Alpine.js port auto-derivation implemented (PRNT-03) — browser verification still pending (Task 3 checkpoint)
- Printer form supports driver dropdown from uploaded drivers
- Grouped printer list ready for 03-02 (script generation)
- Test isolation pattern fixed — future test suites can safely use `list(Model.select().where(...))` for DB assertions
---
*Phase: 03-printer-configuration*
*Completed: 2026-04-10*