zebra-day 0.0.37__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. zebra_day/__init__.py +35 -0
  2. zebra_day/bin/__init__.py +0 -0
  3. zebra_day/cli/__init__.py +240 -0
  4. zebra_day/cli/cognito.py +121 -0
  5. zebra_day/cli/gui.py +338 -0
  6. zebra_day/cli/printer.py +168 -0
  7. zebra_day/cli/template.py +176 -0
  8. zebra_day/cmd_mgr.py +35 -0
  9. zebra_day/etc/Monoid-Regular-HalfTight-Dollar-0-1-l.ttf +0 -0
  10. zebra_day/etc/label_styles/blank.zpl +0 -0
  11. zebra_day/etc/label_styles/cornersStripOf4Squares_1inX1in.zpl +55 -0
  12. zebra_day/etc/label_styles/corners_1inX2in.zpl +28 -0
  13. zebra_day/etc/label_styles/corners_20cmX30cm.zpl +6 -0
  14. zebra_day/etc/label_styles/corners_smallTube.zpl +7 -0
  15. zebra_day/etc/label_styles/corners_unspecifiedDimensions.zpl +15 -0
  16. zebra_day/etc/label_styles/generic_2inX1in.zpl +21 -0
  17. zebra_day/etc/label_styles/plate_1inX0.25in.zpl +9 -0
  18. zebra_day/etc/label_styles/plate_1inX0.25inHD.zpl +9 -0
  19. zebra_day/etc/label_styles/smallTubeWdotHD_prod.zpl +8 -0
  20. zebra_day/etc/label_styles/smallTubeWdot_corners.zpl +7 -0
  21. zebra_day/etc/label_styles/smallTubeWdot_prod.zpl +8 -0
  22. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1.zpl +6 -0
  23. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1b.zpl +3 -0
  24. zebra_day/etc/label_styles/smallTubeWdot_prodV2.zpl +8 -0
  25. zebra_day/etc/label_styles/smallTubeWdot_reagent.zpl +29 -0
  26. zebra_day/etc/label_styles/stripOf4Squares_1inX1in.zpl +32 -0
  27. zebra_day/etc/label_styles/test_800dX800dCoordinateArray.zpl +1 -0
  28. zebra_day/etc/label_styles/tmps/.hold +0 -0
  29. zebra_day/etc/label_styles/tmps/tmp_zpl_templates.here +0 -0
  30. zebra_day/etc/label_styles/tube_20mmX30mmA.zpl +7 -0
  31. zebra_day/etc/label_styles/tube_2inX0.3in.zpl +15 -0
  32. zebra_day/etc/label_styles/tube_2inX0.5in.zpl +15 -0
  33. zebra_day/etc/label_styles/tube_2inX0.5inHD.zpl +15 -0
  34. zebra_day/etc/label_styles/tube_2inX1in.zpl +25 -0
  35. zebra_day/etc/label_styles/tube_2inX1inHD.zpl +22 -0
  36. zebra_day/etc/label_styles/tube_2inX1inHDv3.zpl +21 -0
  37. zebra_day/etc/old_printer_config/.hold +0 -0
  38. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.022846_printer_config.json +1 -0
  39. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.033657_printer_config.json +1 -0
  40. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.039597_printer_config.json +3 -0
  41. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.047295_printer_config.json +1 -0
  42. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.055804_printer_config.json +1 -0
  43. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.061337_printer_config.json +3 -0
  44. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.073326_printer_config.json +1 -0
  45. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.081950_printer_config.json +1 -0
  46. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.088251_printer_config.json +3 -0
  47. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.096501_printer_config.json +1 -0
  48. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.104767_printer_config.json +1 -0
  49. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.110364_printer_config.json +3 -0
  50. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.118239_printer_config.json +1 -0
  51. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.125950_printer_config.json +1 -0
  52. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.349866_printer_config.json +1 -0
  53. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.361085_printer_config.json +3 -0
  54. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.558323_printer_config.json +1 -0
  55. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.565756_printer_config.json +3 -0
  56. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.739070_printer_config.json +16 -0
  57. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.753796_printer_config.json +1 -0
  58. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.760201_printer_config.json +3 -0
  59. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.768747_printer_config.json +1 -0
  60. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.775312_printer_config.json +3 -0
  61. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.782533_printer_config.json +1 -0
  62. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.789287_printer_config.json +1 -0
  63. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.794230_printer_config.json +3 -0
  64. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.800021_printer_config.json +5 -0
  65. zebra_day/etc/printer_config.json +4 -0
  66. zebra_day/etc/printer_config.template.json +24 -0
  67. zebra_day/etc/tmp_printers0.json +5 -0
  68. zebra_day/etc/tmp_printers120.json +10 -0
  69. zebra_day/etc/tmp_printers145.json +10 -0
  70. zebra_day/etc/tmp_printers207.json +10 -0
  71. zebra_day/etc/tmp_printers374.json +5 -0
  72. zebra_day/etc/tmp_printers383.json +5 -0
  73. zebra_day/etc/tmp_printers450.json +5 -0
  74. zebra_day/etc/tmp_printers469.json +10 -0
  75. zebra_day/etc/tmp_printers485.json +10 -0
  76. zebra_day/etc/tmp_printers504.json +5 -0
  77. zebra_day/etc/tmp_printers531.json +10 -0
  78. zebra_day/etc/tmp_printers540.json +10 -0
  79. zebra_day/etc/tmp_printers542.json +10 -0
  80. zebra_day/etc/tmp_printers552.json +10 -0
  81. zebra_day/etc/tmp_printers608.json +5 -0
  82. zebra_day/etc/tmp_printers657.json +5 -0
  83. zebra_day/etc/tmp_printers715.json +10 -0
  84. zebra_day/etc/tmp_printers838.json +5 -0
  85. zebra_day/etc/tmp_printers839.json +5 -0
  86. zebra_day/etc/tmp_printers933.json +5 -0
  87. zebra_day/etc/tmp_printers957.json +5 -0
  88. zebra_day/etc/tmp_printers972.json +10 -0
  89. zebra_day/exceptions.py +88 -0
  90. zebra_day/files/.hold +0 -0
  91. zebra_day/files/blank_preview.png +0 -0
  92. zebra_day/files/corners_20cmX30cm_preview.png +0 -0
  93. zebra_day/files/generic_2inX1in_preview.png +0 -0
  94. zebra_day/files/hold +0 -0
  95. zebra_day/files/test_png_12020.png +0 -0
  96. zebra_day/files/test_png_12352.png +0 -0
  97. zebra_day/files/test_png_15472.png +0 -0
  98. zebra_day/files/test_png_17696.png +0 -0
  99. zebra_day/files/test_png_23477.png +0 -0
  100. zebra_day/files/test_png_24493.png +0 -0
  101. zebra_day/files/test_png_28157.png +0 -0
  102. zebra_day/files/test_png_30069.png +0 -0
  103. zebra_day/files/test_png_35832.png +0 -0
  104. zebra_day/files/test_png_36400.png +0 -0
  105. zebra_day/files/test_png_40816.png +0 -0
  106. zebra_day/files/test_png_47791.png +0 -0
  107. zebra_day/files/test_png_47799.png +0 -0
  108. zebra_day/files/test_png_49564.png +0 -0
  109. zebra_day/files/test_png_53848.png +0 -0
  110. zebra_day/files/test_png_55588.png +0 -0
  111. zebra_day/files/test_png_58809.png +0 -0
  112. zebra_day/files/test_png_62542.png +0 -0
  113. zebra_day/files/test_png_67242.png +0 -0
  114. zebra_day/files/test_png_89893.png +0 -0
  115. zebra_day/files/test_png_91597.png +0 -0
  116. zebra_day/files/test_png_93633.png +0 -0
  117. zebra_day/files/tmpbjo3k7q1.png +0 -0
  118. zebra_day/files/tmpigtr4pwy.png +0 -0
  119. zebra_day/files/tube_20mmX30mmA_preview.png +0 -0
  120. zebra_day/files/zpl_label_tube_2inX1in_2026-02-01_01:51:24.370964.png +0 -0
  121. zebra_day/logging_config.py +74 -0
  122. zebra_day/logs/.hold +0 -0
  123. zebra_day/logs/print_requests.log +2 -0
  124. zebra_day/paths.py +143 -0
  125. zebra_day/print_mgr.py +557 -117
  126. zebra_day/static/datschund.css +140 -0
  127. zebra_day/static/datschund.png +0 -0
  128. zebra_day/static/daylily.png +0 -0
  129. zebra_day/static/favicon.svg +20 -0
  130. zebra_day/static/general.css +99 -0
  131. zebra_day/static/js/zebra_modern.js +172 -0
  132. zebra_day/static/lsmc.css +354 -0
  133. zebra_day/static/moon.jpeg +0 -0
  134. zebra_day/static/oakland.css +197 -0
  135. zebra_day/static/petrichor.css +150 -0
  136. zebra_day/static/popday_daylily.css +140 -0
  137. zebra_day/static/style.css +183 -0
  138. zebra_day/static/triangles.css +122 -0
  139. zebra_day/static/tron.css +277 -0
  140. zebra_day/static/zebra_modern.css +771 -0
  141. zebra_day/static/zebras.css +176 -0
  142. zebra_day/templates/modern/base.html +98 -0
  143. zebra_day/templates/modern/config.html +141 -0
  144. zebra_day/templates/modern/config_backups.html +59 -0
  145. zebra_day/templates/modern/config_editor.html +95 -0
  146. zebra_day/templates/modern/config_new.html +93 -0
  147. zebra_day/templates/modern/dashboard.html +160 -0
  148. zebra_day/templates/modern/print_request.html +145 -0
  149. zebra_day/templates/modern/print_result.html +88 -0
  150. zebra_day/templates/modern/printer_detail.html +244 -0
  151. zebra_day/templates/modern/printers.html +144 -0
  152. zebra_day/templates/modern/save_result.html +46 -0
  153. zebra_day/templates/modern/template_editor.html +175 -0
  154. zebra_day/templates/modern/templates.html +122 -0
  155. zebra_day/web/__init__.py +9 -0
  156. zebra_day/web/app.py +248 -0
  157. zebra_day/web/auth.py +172 -0
  158. zebra_day/web/middleware.py +159 -0
  159. zebra_day/web/routers/__init__.py +2 -0
  160. zebra_day/web/routers/api.py +313 -0
  161. zebra_day/web/routers/ui.py +636 -0
  162. zebra_day/zpl_renderer.py +273 -0
  163. zebra_day-2.0.0.dist-info/METADATA +847 -0
  164. zebra_day-2.0.0.dist-info/RECORD +168 -0
  165. {zebra_day-0.0.37.dist-info → zebra_day-2.0.0.dist-info}/WHEEL +1 -1
  166. zebra_day-2.0.0.dist-info/entry_points.txt +4 -0
  167. zebra_day/bin/scan_for_networed_zebra_printers.py +0 -23
  168. zebra_day/bin/te.py +0 -905
  169. zebra_day/bin/zserve.py +0 -620
  170. zebra_day-0.0.37.dist-info/METADATA +0 -1177
  171. zebra_day-0.0.37.dist-info/RECORD +0 -10
  172. {zebra_day-0.0.37.dist-info → zebra_day-2.0.0.dist-info/licenses}/LICENSE +0 -0
  173. {zebra_day-0.0.37.dist-info → zebra_day-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,313 @@
1
+ """
2
+ Versioned JSON API router for zebra_day.
3
+
4
+ Provides programmatic access to printer management and label printing.
5
+ All endpoints return JSON and are prefixed with /api/v1/.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Optional, List, Dict, Any
10
+
11
+ from fastapi import APIRouter, HTTPException, Request
12
+ from pydantic import BaseModel, Field
13
+
14
+ router = APIRouter()
15
+
16
+
17
+ # ----- Request/Response Models -----
18
+
19
+ class PrintRequest(BaseModel):
20
+ """Request model for printing a label."""
21
+ lab: str = Field(..., description="Lab identifier")
22
+ printer: str = Field(..., description="Printer name")
23
+ label_zpl_style: Optional[str] = Field(None, description="ZPL template name")
24
+ uid_barcode: str = Field("", description="UID for barcode")
25
+ alt_a: str = Field("", description="Alternative field A")
26
+ alt_b: str = Field("", description="Alternative field B")
27
+ alt_c: str = Field("", description="Alternative field C")
28
+ alt_d: str = Field("", description="Alternative field D")
29
+ alt_e: str = Field("", description="Alternative field E")
30
+ alt_f: str = Field("", description="Alternative field F")
31
+ copies: int = Field(1, ge=1, le=100, description="Number of copies")
32
+
33
+
34
+ class PrintResponse(BaseModel):
35
+ """Response model for print request."""
36
+ success: bool
37
+ message: str
38
+ png_url: Optional[str] = None
39
+
40
+
41
+ class PrinterInfo(BaseModel):
42
+ """Printer information model (v2.0.0 schema)."""
43
+ id: str = Field(..., description="Printer identifier/key in JSON")
44
+ ip_address: str
45
+ printer_name: Optional[str] = Field(None, description="User-friendly display name")
46
+ lab_location: Optional[str] = Field(None, description="Location within the lab")
47
+ manufacturer: str = Field("zebra", description="Printer manufacturer")
48
+ model: str
49
+ serial: str
50
+ label_zpl_styles: List[str]
51
+ default_label_style: Optional[str] = Field(None, description="Default label style to use when none specified")
52
+ print_method: str
53
+ notes: Optional[str] = Field("", description="Optional notes")
54
+
55
+
56
+ class LabInfo(BaseModel):
57
+ """Lab information model (v2.0.0 schema)."""
58
+ id: str = Field(..., description="Lab identifier/key in JSON")
59
+ lab_name: str = Field(..., description="Human-readable lab name")
60
+ available_locations: List[str] = Field(default_factory=list, description="Valid location options for printers")
61
+ printers: List[PrinterInfo]
62
+
63
+
64
+ class LabPrinters(BaseModel):
65
+ """Lab and its printers (deprecated, use LabInfo)."""
66
+ lab: str
67
+ printers: List[PrinterInfo]
68
+
69
+
70
+ # ----- Endpoints -----
71
+
72
+ @router.get("/labs", response_model=List[str])
73
+ async def list_labs(request: Request) -> List[str]:
74
+ """List all available labs."""
75
+ zp = request.app.state.zp
76
+ return list(zp.printers.get("labs", {}).keys())
77
+
78
+
79
+ @router.get("/labs/{lab}", response_model=LabInfo)
80
+ async def get_lab(request: Request, lab: str) -> LabInfo:
81
+ """Get lab details including available locations and printers."""
82
+ zp = request.app.state.zp
83
+ labs = zp.printers.get("labs", {})
84
+
85
+ if lab not in labs:
86
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
87
+
88
+ lab_data = labs[lab]
89
+ lab_printers = lab_data.get("printers", {})
90
+
91
+ printers = []
92
+ for printer_id, info in lab_printers.items():
93
+ printers.append(
94
+ PrinterInfo(
95
+ id=printer_id,
96
+ ip_address=info.get("ip_address", ""),
97
+ printer_name=info.get("printer_name"),
98
+ lab_location=info.get("lab_location"),
99
+ manufacturer=info.get("manufacturer", "zebra"),
100
+ model=info.get("model", ""),
101
+ serial=info.get("serial", ""),
102
+ label_zpl_styles=info.get("label_zpl_styles", []),
103
+ default_label_style=info.get("default_label_style"),
104
+ print_method=info.get("print_method", ""),
105
+ notes=info.get("notes", ""),
106
+ )
107
+ )
108
+
109
+ return LabInfo(
110
+ id=lab,
111
+ lab_name=lab_data.get("lab_name", lab),
112
+ available_locations=lab_data.get("available_locations", []),
113
+ printers=printers,
114
+ )
115
+
116
+
117
+ @router.get("/labs/{lab}/printers", response_model=List[PrinterInfo])
118
+ async def list_printers(request: Request, lab: str) -> List[PrinterInfo]:
119
+ """List all printers in a lab."""
120
+ zp = request.app.state.zp
121
+ labs = zp.printers.get("labs", {})
122
+
123
+ if lab not in labs:
124
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
125
+
126
+ # Access printers via nested 'printers' key (v2 schema)
127
+ lab_printers = labs[lab].get("printers", {})
128
+
129
+ printers = []
130
+ for printer_id, info in lab_printers.items():
131
+ printers.append(
132
+ PrinterInfo(
133
+ id=printer_id,
134
+ ip_address=info.get("ip_address", ""),
135
+ printer_name=info.get("printer_name"),
136
+ lab_location=info.get("lab_location"),
137
+ manufacturer=info.get("manufacturer", "zebra"),
138
+ model=info.get("model", ""),
139
+ serial=info.get("serial", ""),
140
+ label_zpl_styles=info.get("label_zpl_styles", []),
141
+ default_label_style=info.get("default_label_style"),
142
+ print_method=info.get("print_method", ""),
143
+ notes=info.get("notes", ""),
144
+ )
145
+ )
146
+ return printers
147
+
148
+
149
+ @router.get("/templates", response_model=List[str])
150
+ async def list_templates(request: Request) -> List[str]:
151
+ """List all available ZPL templates."""
152
+ from pathlib import Path
153
+
154
+ pkg_path = request.app.state.pkg_path
155
+ styles_dir = pkg_path / "etc" / "label_styles"
156
+
157
+ templates = []
158
+ if styles_dir.exists():
159
+ for f in styles_dir.iterdir():
160
+ if f.is_file() and f.suffix == ".zpl":
161
+ templates.append(f.stem)
162
+
163
+ # Also include drafts
164
+ tmps_dir = styles_dir / "tmps"
165
+ if tmps_dir.exists():
166
+ for f in tmps_dir.iterdir():
167
+ if f.is_file() and f.suffix == ".zpl":
168
+ templates.append(f.stem)
169
+
170
+ return sorted(templates)
171
+
172
+
173
+ @router.post("/print", response_model=PrintResponse)
174
+ async def print_label(request: Request, print_req: PrintRequest) -> PrintResponse:
175
+ """Send a print request to a printer."""
176
+ zp = request.app.state.zp
177
+ rate_limiter = request.app.state.print_rate_limiter
178
+ client_ip = request.client.host if request.client else "unknown"
179
+
180
+ # Check rate limit
181
+ allowed, reason = await rate_limiter.acquire(client_ip)
182
+ if not allowed:
183
+ raise HTTPException(status_code=429, detail=reason)
184
+
185
+ try:
186
+ result = zp.print_zpl(
187
+ lab=print_req.lab,
188
+ printer_name=print_req.printer,
189
+ label_zpl_style=print_req.label_zpl_style,
190
+ uid_barcode=print_req.uid_barcode,
191
+ alt_a=print_req.alt_a,
192
+ alt_b=print_req.alt_b,
193
+ alt_c=print_req.alt_c,
194
+ alt_d=print_req.alt_d,
195
+ alt_e=print_req.alt_e,
196
+ alt_f=print_req.alt_f,
197
+ print_n=print_req.copies,
198
+ client_ip=client_ip,
199
+ )
200
+
201
+ # Check if result is a PNG file path
202
+ if result and ".png" in str(result):
203
+ png_name = str(result).split("/")[-1]
204
+ return PrintResponse(
205
+ success=True,
206
+ message="PNG generated successfully",
207
+ png_url=f"/files/{png_name}",
208
+ )
209
+
210
+ return PrintResponse(success=True, message="Print request sent successfully")
211
+ except Exception as e:
212
+ raise HTTPException(status_code=500, detail=str(e))
213
+ finally:
214
+ rate_limiter.release()
215
+
216
+
217
+ @router.get("/config")
218
+ async def get_config(request: Request) -> Dict[str, Any]:
219
+ """Get the current printer configuration."""
220
+ zp = request.app.state.zp
221
+ return zp.printers
222
+
223
+
224
+ # ----- Lab Settings Endpoints -----
225
+
226
+ class LabUpdateRequest(BaseModel):
227
+ """Request model for updating lab settings."""
228
+ lab_name: Optional[str] = Field(None, description="Human-readable lab name")
229
+ available_locations: Optional[List[str]] = Field(None, description="List of valid locations")
230
+
231
+
232
+ class PrinterUpdateRequest(BaseModel):
233
+ """Request model for updating printer settings."""
234
+ printer_name: Optional[str] = Field(None, description="User-friendly display name")
235
+ lab_location: Optional[str] = Field(None, description="Location within the lab")
236
+ notes: Optional[str] = Field(None, description="Optional notes")
237
+ label_zpl_styles: Optional[List[str]] = Field(None, description="Allowed ZPL styles")
238
+ default_label_style: Optional[str] = Field(None, description="Default label style to use when none specified")
239
+
240
+
241
+ @router.patch("/labs/{lab}", response_model=LabInfo)
242
+ async def update_lab(request: Request, lab: str, update: LabUpdateRequest) -> LabInfo:
243
+ """Update lab settings (lab_name, available_locations)."""
244
+ zp = request.app.state.zp
245
+ labs = zp.printers.get("labs", {})
246
+
247
+ if lab not in labs:
248
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
249
+
250
+ lab_data = labs[lab]
251
+
252
+ if update.lab_name is not None:
253
+ lab_data["lab_name"] = update.lab_name
254
+ if update.available_locations is not None:
255
+ lab_data["available_locations"] = update.available_locations
256
+
257
+ # Save changes
258
+ zp.save_printer_json(zp.printers_filename, relative=False)
259
+
260
+ # Return updated lab info
261
+ return await get_lab(request, lab)
262
+
263
+
264
+ @router.patch("/labs/{lab}/printers/{printer_id}")
265
+ async def update_printer(
266
+ request: Request, lab: str, printer_id: str, update: PrinterUpdateRequest
267
+ ) -> PrinterInfo:
268
+ """Update printer settings (printer_name, lab_location, notes)."""
269
+ zp = request.app.state.zp
270
+ labs = zp.printers.get("labs", {})
271
+
272
+ if lab not in labs:
273
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
274
+
275
+ lab_printers = labs[lab].get("printers", {})
276
+ if printer_id not in lab_printers:
277
+ raise HTTPException(status_code=404, detail=f"Printer '{printer_id}' not found in lab '{lab}'")
278
+
279
+ printer_data = lab_printers[printer_id]
280
+
281
+ if update.printer_name is not None:
282
+ printer_data["printer_name"] = update.printer_name if update.printer_name else None
283
+ if update.lab_location is not None:
284
+ printer_data["lab_location"] = update.lab_location if update.lab_location else None
285
+ if update.notes is not None:
286
+ printer_data["notes"] = update.notes
287
+ if update.label_zpl_styles is not None:
288
+ printer_data["label_zpl_styles"] = update.label_zpl_styles
289
+ if update.default_label_style is not None:
290
+ # Validate that the style exists in label_zpl_styles (if it's not empty string)
291
+ if update.default_label_style and update.default_label_style not in printer_data.get("label_zpl_styles", []):
292
+ raise HTTPException(
293
+ status_code=400,
294
+ detail=f"Default label style '{update.default_label_style}' must be one of: {printer_data.get('label_zpl_styles', [])}"
295
+ )
296
+ printer_data["default_label_style"] = update.default_label_style if update.default_label_style else None
297
+
298
+ # Save changes
299
+ zp.save_printer_json(zp.printers_filename, relative=False)
300
+
301
+ return PrinterInfo(
302
+ id=printer_id,
303
+ ip_address=printer_data.get("ip_address", ""),
304
+ printer_name=printer_data.get("printer_name"),
305
+ lab_location=printer_data.get("lab_location"),
306
+ manufacturer=printer_data.get("manufacturer", "zebra"),
307
+ model=printer_data.get("model", ""),
308
+ serial=printer_data.get("serial", ""),
309
+ label_zpl_styles=printer_data.get("label_zpl_styles", []),
310
+ default_label_style=printer_data.get("default_label_style"),
311
+ print_method=printer_data.get("print_method", ""),
312
+ notes=printer_data.get("notes", ""),
313
+ )