Skip to content

refactor: enforce layer boundaries so adapters stop reaching into each other - #99

Merged
DLANSAMA merged 1 commit into
mainfrom
refactor/layer-boundaries
Aug 5, 2026
Merged

refactor: enforce layer boundaries so adapters stop reaching into each other#99
DLANSAMA merged 1 commit into
mainfrom
refactor/layer-boundaries

Conversation

@DLANSAMA

@DLANSAMA DLANSAMA commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Step 1 of 4 in a structural refactor. Draft on purpose — see sequencing below.

Why

protocols/, slicer/ and download/ were already separate directories. Nothing enforced the separation, and it had drifted:

slicer/output.py:17   from bambu_cli.protocols.ftps import _remove_partial_file

The OrcaSlicer runner imported a private Bambu FTPS helper to delete a partial file, so a change to printer transport code silently changed slicer behavior. Five more of the same shape, four of them hidden behind function-local imports.

The load-bearing change

scripts/check_layers.py, now blocking in the lint job. It ranks every module and rejects any upward import, plus any import between the three sibling adapters. Deferred (function-local) imports count — they break the import cycle, not the dependency.

70  cli.py (sys.exit lives here)          80  bambu.py (__main__ shim)
50  commands/ interactive/ tui/           45  job/     40  download/ setup_cmd/
35  printer.py                            30  protocols/ | slicer/ | printables/   <- must not import each other
25  cliparse.py                           20  utils config context netsafety ams
10  constants errors paths logging_utils argutils jsonio tlspin fsutil

Directories were never the mechanism. This is.

Changes

Fix Effect
New fsutil.py (rank 10) The four pure path/file helpers stranded in protocols/ftps and download/naming
New cliparse.py The argparse tree; cli.py drops 728 -> 308 lines and stays the only module with sys.exit
camera.py -> protocols/camera.py It is a TLS transport sharing tlspin
get_ftp(printer), monitor_status(args, printer) Take the printer instead of reaching up for an ambient one
JobSteps commands.cmd_job is the composition root; a missing step raises MissingJobStep rather than importing across a layer
DIRECT_CAMERA_MODELS -> constants.py doctor and preflight both read it without an upward import

Behavior is unchanged. Two tests were injecting printers by patching bambu_cli.printer.get_printer — module-global patching the repo guidance forbids — and now inject directly.

Gates

1146 passed, 1 deselected     (baseline 1145; +1 is a new missing-step test)
coverage 86.2%               (baseline 84.6%)
ruff / ruff format / mypy / bandit / syntax / cli-help / ci-workflow    green
check_layers.py: Layer boundaries OK (1 allowlisted debt edge)

Deliberately not fixed

  • context -> printerRuntimeContext lazily builds a BambuPrinter. Allowlisted in the checker with a written reason; the real fix is a composition root installing a printer factory, which would have made this 39-file diff unreviewable.
  • protocols/mqtt.py (~880 LOC) — same reasoning: not splitting a hotspot inside a boundary refactor.

Both are now recorded in AGENTS.md under known architecture debt instead of being quietly dropped.

Sequencing

Draft because #97 (feat/tui) should merge first. This branch moves camera.py, splits cli.py, and inverts the commands <-> interactive edge; tui/ imports interactive, commands and protocols, so whichever lands second resolves conflicts. Once #97 is in, I will rebase this onto main and mark it ready.

…h other

The package already had protocols/, slicer/ and download/ as separate
directories, but nothing enforced the separation and it had drifted:

  - slicer/output.py imported a *private FTPS helper* to delete a partial
    file, so a change to Bambu transport code silently changed slicer
    behavior — exactly the coupling the split was meant to prevent
  - slicer/options.py reached into download/naming for a basename helper
  - download/ pulled three private filesystem helpers out of protocols/ftps
  - protocols/ftps and protocols/mqtt reached *up* to bambu_cli.printer for
    an ambient get_printer(), making them untestable in isolation
  - job/steps.py late-bound to bambu_cli.commands for its default handlers
  - interactive/ imported cli.build_parser, dragging the entrypoint (and its
    sys.exit) into the domain layer

Rather than move files and hope, scripts/check_layers.py now assigns every
module a rank and fails CI on any upward import or any import between the
three sibling adapters. Deferred (function-local) imports count: they break
the import cycle, not the dependency.

Changes:

  - new fsutil.py (rank 10) holds the four pure path/file helpers that were
    stranded in protocols/ftps and download/naming
  - new cliparse.py holds the argparse tree; cli.py drops 728 -> 308 lines
    and keeps main()/dispatch, so it remains the only module with sys.exit
  - camera.py -> protocols/camera.py (it is a TLS transport sharing tlspin)
  - get_ftp(printer) and monitor_status(args, printer) now take the printer
    instead of looking one up; the two callers that omitted it pass one
  - JobSteps no longer defaults to bambu_cli.commands handlers. commands.cmd_job
    is the composition root and wires them; a missing step raises MissingJobStep
    instead of silently importing across a layer
  - DIRECT_CAMERA_MODELS moved to constants.py so doctor and preflight can
    both read it without an upward import

Behavior is unchanged. Tests that patched bambu_cli.printer.get_printer to
inject a printer now pass one directly.

One edge stays allowlisted with a reason: context -> printer (RuntimeContext
lazily builds a BambuPrinter). Fixing it needs a composition root that installs
a printer factory, which would have made this diff unreviewable.

Gates: 1146 passed (baseline 1145; +1 new test), coverage 86.2% (was 84.6%),
ruff/ruff-format/mypy/bandit/syntax/help/workflow smokes all green.
@DLANSAMA
DLANSAMA force-pushed the refactor/layer-boundaries branch from 7084611 to a066582 Compare August 5, 2026 12:49
@DLANSAMA
DLANSAMA marked this pull request as ready for review August 5, 2026 14:20
@DLANSAMA
DLANSAMA merged commit d8566db into main Aug 5, 2026
6 checks passed
@DLANSAMA
DLANSAMA deleted the refactor/layer-boundaries branch August 5, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant