feat(printer-models): Brother PT-Series TapeRegistry with TZe and heat-shrink specs#47
Conversation
…t-shrink specs Adds a pure-data lookup module (tape_specs_pt.py) with manufacturer-published print-area tables for TZe laminated/non-laminated tapes (4–24 mm) and heat-shrink 2:1 tapes (6–24 mm), and a static TapeRegistry.lookup_pt() that maps (width_mm, MediaType) → TapeSpec or raises UnknownTapeError. HS 3:1 and QL-Series die-cut labels are explicitly out of scope pending Phase 2 hardware tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ON_LAMINATED test - Change PT_TZE_TAPES and PT_HS_2_1_TAPES from list[TapeSpec] to tuple[TapeSpec, ...] — module-level manufacturer constants must not be mutable at runtime; tuple makes that intent explicit and enforced. - Add slots=True to @DataClass(frozen=True) on TapeSpec to match the project convention established in status_block.py (line 167). - Document the unregistered catch-all branch in tape_registry.py so a future contributor adding HEAT_SHRINK_3_1 or QL-Series support knows exactly where to wire it in. - Add test_lookup_pt_series_non_laminated_uses_tze_table to cover the NON_LAMINATED dispatch path and pin the intentional aliasing behaviour: a NON_LAMINATED query returns a LAMINATED-typed spec because both share the same TZe geometry table. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request establishes the foundational lookup mechanism for Brother PT-Series printer tape specifications. By centralizing manufacturer-provided geometry and constraints into an immutable registry, the system ensures consistent and reliable printer configuration. The changes focus on providing a clean, type-safe interface for retrieving tape-specific parameters while maintaining strict adherence to documented hardware specs. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request adds a tape registry and specifications for Brother PT-Series printers, covering TZe and heat-shrink tapes. The review identifies architectural violations of Rule 17, recommending that model-specific data and logic be moved to the appropriate plugin directory. It also suggests using dataclasses.replace to ensure the TapeSpec identity matches the requested media_type and updating the unit tests accordingly.
| """Tape specifications for Brother PT-Series printers (180 DPI, 128-pin head). | ||
|
|
||
| Source: Brother Raster Command Reference (PT-E550W / PT-P710BT / PT-P750W) v1.02. | ||
| The numbers here come straight from the manufacturer's printable-area tables. | ||
| """ |
There was a problem hiding this comment.
This file contains model-specific data and specifications for the PT-Series. According to the Repository Style Guide (Rule 17), model-specific code must reside in app/printer_models/<series>.py.
Consider moving the PT_TZE_TAPES and PT_HS_2_1_TAPES tuples to app/printer_models/pt.py. The TapeSpec dataclass definition itself could move to a more central location (like app/services/tape_registry.py or a new app/models/tape.py) if it is intended to be shared with other series like QL.
References
- Model-specific code must be located within the appropriate plugin file in
app/printer_models/. (link)
| from __future__ import annotations | ||
|
|
||
| from app.services.status_block import MediaType | ||
| from app.services.tape_specs_pt import ( | ||
| PT_HS_2_1_TAPES, | ||
| PT_TZE_TAPES, | ||
| TapeSpec, | ||
| ) |
There was a problem hiding this comment.
Add import dataclasses to support returning a spec with the correct media_type identity for aliased types like NON_LAMINATED.
| from __future__ import annotations | |
| from app.services.status_block import MediaType | |
| from app.services.tape_specs_pt import ( | |
| PT_HS_2_1_TAPES, | |
| PT_TZE_TAPES, | |
| TapeSpec, | |
| ) | |
| from __future__ import annotations | |
| import dataclasses | |
| from app.services.status_block import MediaType | |
| from app.services.tape_specs_pt import ( | |
| PT_HS_2_1_TAPES, | |
| PT_TZE_TAPES, | |
| TapeSpec, | |
| ) |
| def lookup_pt(width_mm: int, media_type: MediaType) -> TapeSpec: | ||
| """Return the PT-Series tape spec for the given width + media type. | ||
|
|
||
| Non-laminated TZe-N tapes share TZe laminated dimensions, so both | ||
| MediaType.LAMINATED and MediaType.NON_LAMINATED resolve to the same | ||
| PT_TZE_TAPES table. | ||
| """ | ||
| if media_type in (MediaType.LAMINATED, MediaType.NON_LAMINATED): | ||
| table = PT_TZE_TAPES | ||
| elif media_type == MediaType.HEAT_SHRINK_2_1: | ||
| table = PT_HS_2_1_TAPES | ||
| else: | ||
| # MediaType.HEAT_SHRINK_3_1 and QL-Series types are intentionally | ||
| # unregistered — add a new table and branch here when supported. | ||
| raise UnknownTapeError(f"No PT-Series tape table for media_type={media_type.name}") |
There was a problem hiding this comment.
The lookup_pt method contains hardcoded logic mapping MediaType to PT-specific tables. Per Rule 17 of the Repository Style Guide, this model-specific logic should reside in the PT plugin. Consider refactoring TapeRegistry to be a generic dispatcher that delegates to series-specific plugins, keeping the core registry decoupled from specific hardware details.
References
- Model-specific code must be located within the appropriate plugin file in
app/printer_models/. (link)
| for spec in table: | ||
| if spec.width_mm == width_mm: | ||
| return spec |
There was a problem hiding this comment.
When media_type is MediaType.NON_LAMINATED, the registry currently returns a TapeSpec where media_type is LAMINATED. This identity mismatch can be confusing for downstream consumers. Use dataclasses.replace to ensure the returned object reflects the requested media_type.
| for spec in table: | |
| if spec.width_mm == width_mm: | |
| return spec | |
| for spec in table: | |
| if spec.width_mm == width_mm: | |
| return dataclasses.replace(spec, media_type=media_type) if spec.media_type != media_type else spec |
| """MediaType.NON_LAMINATED resolves via the TZe table (same geometry).""" | ||
| spec = TapeRegistry.lookup_pt(width_mm=12, media_type=MediaType.NON_LAMINATED) | ||
| assert spec.print_area_pins == 70 # same as 12mm laminated TZe | ||
| assert spec.media_type == MediaType.LAMINATED # returned spec is the laminated record |
There was a problem hiding this comment.
Pull request overview
Adds a PT-Series tape specification “lookup database” for Brother media widths/types, exposing an immutable TapeSpec record and a TapeRegistry.lookup_pt() API, with unit tests to validate a few key geometry values and error cases.
Changes:
- Introduces
TapeSpecplus immutable PT-Series spec tables for TZe and heat-shrink 2:1 media. - Adds
TapeRegistry.lookup_pt(width_mm, media_type)andUnknownTapeErrorfor unsupported media/width combinations. - Adds unit tests covering common lookups, aliasing of
NON_LAMINATED, and unknown-width failures.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| backend/app/services/tape_specs_pt.py | Adds frozen TapeSpec dataclass and PT-Series tape spec tables (TZe + heat shrink 2:1). |
| backend/app/services/tape_registry.py | Adds registry lookup API and error type for mapping (width, media_type) → TapeSpec. |
| backend/tests/unit/services/test_tape_registry.py | Adds unit tests for PT-Series tape spec lookup behavior and error handling. |
| """Lookup of Brother tape specifications by physical width + media type. | ||
|
|
||
| QL-Series die-cut labels will be added once Phase 2 hardware tests confirm the | ||
| status-block byte sequence — see `docs/superpowers/plans/2026-05-11-label-printer-hub.md`. |
| """Return the PT-Series tape spec for the given width + media type. | ||
|
|
||
| Non-laminated TZe-N tapes share TZe laminated dimensions, so both | ||
| MediaType.LAMINATED and MediaType.NON_LAMINATED resolve to the same | ||
| PT_TZE_TAPES table. | ||
| """ | ||
| if media_type in (MediaType.LAMINATED, MediaType.NON_LAMINATED): | ||
| table = PT_TZE_TAPES | ||
| elif media_type == MediaType.HEAT_SHRINK_2_1: | ||
| table = PT_HS_2_1_TAPES |
…ix media_type identity, drop private doc link Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## 0.3.0 (2026-05-12) * feat(config): pydantic-settings module with env-driven runtime configuration (#45) ([878e9e0](878e9e0)), closes [#45](#45) * feat(integrations): AppLookupService aggregator — Phase 3 complete (#53) ([222bef4](222bef4)), closes [#53](#53) * feat(integrations): Grocy + Spoolman lookup clients with shared NotFoundError base (#52) ([b1c9c3c](b1c9c3c)), closes [#52](#52) * feat(integrations): LabelData schema + Snipe-IT lookup client (#51) ([3bc180f](3bc180f)), closes [#51](#51) * feat(label-renderer): Template schema + Pillow/qrcode renderer for 1-bit label bitmaps (#54) ([fb77028](fb77028)), closes [#54](#54) * feat(printer-models): Brother PT-Series TapeRegistry with TZe and heat-shrink specs (#47) ([7526019](7526019)), closes [#47](#47) * feat(printer-models): Job lifecycle FSM with explicit state machine (#49) ([1a8c40e](1a8c40e)), closes [#49](#49) * feat(printer-models): PrinterModel Protocol + ModelRegistry for plugin discovery (#48) ([2ae0e09](2ae0e09)), closes [#48](#48) * feat(printer-models): PrintQueue worker with pause/resume/cancel/retry (#50) ([dfdf6fe](dfdf6fe)), closes [#50](#50) [skip ci]
Summary
Phase 2 Task 2.6 from the plan. Pure lookup database for Brother PT-Series tape specifications.
TapeRegistry.lookup_pt(width_mm, media_type)returns a frozenTapeSpecrecord with print-area geometry, byte-stride, length bounds, and cutter minimum.Source: Brother Raster Command Reference v1.02 (PT-E550W / PT-P710BT / PT-P750W). No empirical learning — every number is manufacturer-published.
What's in this PR
backend/app/services/tape_specs_pt.py@dataclass(frozen=True, slots=True)TapeSpecrecord matchingstatus_block.py's convention.PT_TZE_TAPES: tuple[TapeSpec, ...]— 6 entries (4, 6, 9, 12, 18, 24 mm laminated TZe).PT_HS_2_1_TAPES: tuple[TapeSpec, ...]— 5 entries for heat-shrink 2:1.tuplerather thanlistso the manufacturer tables can't be mutated at runtime (frozen records inside an immutable container).backend/app/services/tape_registry.pyUnknownTapeError(Exception)for both unknown media types and unknown widths.TapeRegistry.lookup_pt(width_mm, media_type)— static-method lookup:LAMINATEDandNON_LAMINATEDboth resolve viaPT_TZE_TAPES(TZe-N tapes share TZe-laminated geometry).HEAT_SHRINK_2_1resolves viaPT_HS_2_1_TAPES.HEAT_SHRINK_3_1and QL types raiseUnknownTapeError— intentionally unregistered until Phase 2 hardware testing covers them.backend/tests/unit/services/test_tape_registry.pyWhat's NOT in this PR
print_area_pins == print_area_dotspost-init validator (deferred — QL series may legitimately differ).UnknownTapeErrorinto media/width subclasses (premature at 11 entries).Test plan
pytest -q→ 43 passed (42 existing + 5 new — net +1 from spec; the spec specified 4 tests, +1 NON_LAMINATED coverage added in review).pytest tests/unit/services/test_tape_registry.py -v→ 5/5.ruff format --check .clean.ruff check .clean.mypy app/(strict) clean.Review history (subagent-driven)
e6d1df2— initial commit.slots=True, an untested NON_LAMINATED dispatch alias.8cfc0e1— tuple-typed tables,slots=TrueonTapeSpec, NON_LAMINATED test, dead-branch comment.Linked plan
docs/superpowers/plans/2026-05-11-label-printer-hub.mdTask 2.6 (Phase 2 — Plugin Foundation).