zebra-day 0.0.32__py3-none-any.whl → 1.0.2__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 (190) hide show
  1. zebra_day/__init__.py +35 -0
  2. zebra_day/bin/fetch_zebra_config.py +15 -0
  3. zebra_day/bin/generate_coord_grid_zpl.py +50 -0
  4. zebra_day/bin/print_zpl_from_file.py +21 -0
  5. zebra_day/bin/probe_new_label_dimensions.py +75 -0
  6. zebra_day/bin/scan_for_networed_zebra_printers_curl.sh +2 -1
  7. zebra_day/bin/zserve.py +701 -259
  8. zebra_day/cli/__init__.py +240 -0
  9. zebra_day/cli/cognito.py +121 -0
  10. zebra_day/cli/gui.py +255 -0
  11. zebra_day/cli/printer.py +168 -0
  12. zebra_day/cli/template.py +176 -0
  13. zebra_day/cmd_mgr.py +35 -0
  14. zebra_day/etc/label_styles/blank.zpl +0 -0
  15. zebra_day/etc/label_styles/cornersStripOf4Squares_1inX1in.zpl +55 -0
  16. zebra_day/etc/label_styles/corners_1inX2in.zpl +28 -0
  17. zebra_day/etc/label_styles/corners_20cmX30cm.zpl +6 -0
  18. zebra_day/etc/label_styles/corners_smallTube.zpl +7 -0
  19. zebra_day/etc/label_styles/corners_unspecifiedDimensions.zpl +15 -0
  20. zebra_day/etc/label_styles/plate_1inX0.25inHD.zpl +9 -0
  21. zebra_day/etc/label_styles/smallTubeWdotHD_prod.zpl +8 -0
  22. zebra_day/etc/label_styles/smallTubeWdot_corners.zpl +7 -0
  23. zebra_day/etc/label_styles/smallTubeWdot_prod.zpl +8 -0
  24. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1.zpl +6 -0
  25. zebra_day/etc/label_styles/smallTubeWdot_prodAlt1b.zpl +3 -0
  26. zebra_day/etc/label_styles/smallTubeWdot_prodV2.zpl +8 -0
  27. zebra_day/etc/label_styles/smallTubeWdot_reagent.zpl +29 -0
  28. zebra_day/etc/label_styles/stripOf4Squares_1inX1in.zpl +32 -0
  29. zebra_day/etc/label_styles/test_800dX800dCoordinateArray.zpl +1 -0
  30. zebra_day/etc/label_styles/tmps/tmp_zpl_templates.here +0 -0
  31. zebra_day/etc/label_styles/tube_20mmX30mmA.zpl +7 -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_2inX1inHD.zpl +22 -0
  35. zebra_day/etc/label_styles/tube_2inX1inHDv3.zpl +21 -0
  36. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.022846_printer_config.json +1 -0
  37. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.033657_printer_config.json +1 -0
  38. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.039597_printer_config.json +3 -0
  39. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.047295_printer_config.json +1 -0
  40. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.055804_printer_config.json +1 -0
  41. zebra_day/etc/old_printer_config/2026-02-01_01:50:25.061337_printer_config.json +3 -0
  42. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.073326_printer_config.json +1 -0
  43. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.081950_printer_config.json +1 -0
  44. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.088251_printer_config.json +3 -0
  45. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.096501_printer_config.json +1 -0
  46. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.104767_printer_config.json +1 -0
  47. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.110364_printer_config.json +3 -0
  48. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.118239_printer_config.json +1 -0
  49. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.125950_printer_config.json +1 -0
  50. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.349866_printer_config.json +1 -0
  51. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.361085_printer_config.json +3 -0
  52. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.558323_printer_config.json +1 -0
  53. zebra_day/etc/old_printer_config/2026-02-01_01:51:24.565756_printer_config.json +3 -0
  54. zebra_day/etc/old_printer_config/{2023-10-25_02:19:04.139607_printer_config.json → 2026-02-01_01:51:29.739070_printer_config.json} +4 -3
  55. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.753796_printer_config.json +1 -0
  56. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.760201_printer_config.json +3 -0
  57. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.768747_printer_config.json +1 -0
  58. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.775312_printer_config.json +3 -0
  59. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.782533_printer_config.json +1 -0
  60. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.789287_printer_config.json +1 -0
  61. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.794230_printer_config.json +3 -0
  62. zebra_day/etc/old_printer_config/2026-02-01_01:51:29.800021_printer_config.json +5 -0
  63. zebra_day/etc/printer_config.json +3 -54
  64. zebra_day/etc/printer_config.template.json +3 -2
  65. zebra_day/etc/tmp_printers0.json +5 -0
  66. zebra_day/etc/tmp_printers374.json +5 -0
  67. zebra_day/etc/tmp_printers383.json +5 -0
  68. zebra_day/etc/tmp_printers450.json +5 -0
  69. zebra_day/etc/tmp_printers504.json +5 -0
  70. zebra_day/etc/tmp_printers608.json +5 -0
  71. zebra_day/etc/tmp_printers657.json +5 -0
  72. zebra_day/etc/tmp_printers838.json +5 -0
  73. zebra_day/etc/tmp_printers839.json +5 -0
  74. zebra_day/etc/tmp_printers933.json +5 -0
  75. zebra_day/etc/tmp_printers957.json +5 -0
  76. zebra_day/exceptions.py +88 -0
  77. zebra_day/files/hold +0 -0
  78. zebra_day/files/test_png_17696.png +0 -0
  79. zebra_day/files/test_png_23477.png +0 -0
  80. zebra_day/files/test_png_28157.png +0 -0
  81. zebra_day/files/test_png_35832.png +0 -0
  82. zebra_day/files/test_png_36400.png +0 -0
  83. zebra_day/files/test_png_40816.png +0 -0
  84. zebra_day/files/test_png_49564.png +0 -0
  85. zebra_day/files/test_png_53848.png +0 -0
  86. zebra_day/files/test_png_62542.png +0 -0
  87. zebra_day/files/test_png_91597.png +0 -0
  88. zebra_day/files/test_png_93633.png +0 -0
  89. zebra_day/files/tmpbjo3k7q1.png +0 -0
  90. zebra_day/files/tmpigtr4pwy.png +0 -0
  91. zebra_day/files/zpl_label_tube_2inX1in_2026-02-01_01:51:24.370964.png +0 -0
  92. zebra_day/logging_config.py +74 -0
  93. zebra_day/logs/.hold +0 -0
  94. zebra_day/logs/print_requests.log +2 -0
  95. zebra_day/paths.py +143 -0
  96. zebra_day/print_mgr.py +489 -103
  97. zebra_day/static/datschund.css +63 -43
  98. zebra_day/static/datschund.png +0 -0
  99. zebra_day/static/daylily.png +0 -0
  100. zebra_day/static/favicon.svg +20 -0
  101. zebra_day/static/general.css +99 -0
  102. zebra_day/static/js/zebra_modern.js +172 -0
  103. zebra_day/static/lsmc.css +354 -0
  104. zebra_day/static/moon.jpeg +0 -0
  105. zebra_day/static/oakland.css +0 -32
  106. zebra_day/static/popday_daylily.css +1 -1
  107. zebra_day/static/style.css +39 -0
  108. zebra_day/static/zebra_modern.css +771 -0
  109. zebra_day/templates/base.html +36 -0
  110. zebra_day/templates/bpr.html +72 -0
  111. zebra_day/templates/build_new_config.html +36 -0
  112. zebra_day/templates/build_print_request.html +32 -0
  113. zebra_day/templates/chg_ui_style.html +19 -0
  114. zebra_day/templates/edit_template.html +128 -0
  115. zebra_day/templates/edit_zpl.html +37 -0
  116. zebra_day/templates/index.html +82 -0
  117. zebra_day/templates/legacy/base.html +37 -0
  118. zebra_day/templates/legacy/bpr.html +72 -0
  119. zebra_day/templates/legacy/build_new_config.html +36 -0
  120. zebra_day/templates/legacy/build_print_request.html +32 -0
  121. zebra_day/templates/legacy/chg_ui_style.html +19 -0
  122. zebra_day/templates/legacy/edit_template.html +128 -0
  123. zebra_day/templates/legacy/edit_zpl.html +37 -0
  124. zebra_day/templates/legacy/index.html +82 -0
  125. zebra_day/templates/legacy/list_prior_configs.html +24 -0
  126. zebra_day/templates/legacy/print_result.html +30 -0
  127. zebra_day/templates/legacy/printer_details.html +25 -0
  128. zebra_day/templates/legacy/printer_status.html +70 -0
  129. zebra_day/templates/legacy/save_result.html +17 -0
  130. zebra_day/templates/legacy/send_print_request.html +34 -0
  131. zebra_day/templates/legacy/simple_print.html +94 -0
  132. zebra_day/templates/legacy/view_pstation_json.html +29 -0
  133. zebra_day/templates/list_prior_configs.html +24 -0
  134. zebra_day/templates/modern/base.html +98 -0
  135. zebra_day/templates/modern/config.html +141 -0
  136. zebra_day/templates/modern/dashboard.html +160 -0
  137. zebra_day/templates/modern/print_request.html +141 -0
  138. zebra_day/templates/modern/print_result.html +88 -0
  139. zebra_day/templates/modern/printer_detail.html +117 -0
  140. zebra_day/templates/modern/printers.html +133 -0
  141. zebra_day/templates/modern/save_result.html +46 -0
  142. zebra_day/templates/modern/template_editor.html +172 -0
  143. zebra_day/templates/modern/templates.html +122 -0
  144. zebra_day/templates/print_result.html +30 -0
  145. zebra_day/templates/printer_details.html +25 -0
  146. zebra_day/templates/printer_status.html +70 -0
  147. zebra_day/templates/save_result.html +17 -0
  148. zebra_day/templates/send_print_request.html +34 -0
  149. zebra_day/templates/simple_print.html +94 -0
  150. zebra_day/templates/view_pstation_json.html +29 -0
  151. zebra_day/web/__init__.py +9 -0
  152. zebra_day/web/app.py +171 -0
  153. zebra_day/web/auth.py +172 -0
  154. zebra_day/web/middleware.py +159 -0
  155. zebra_day/web/routers/__init__.py +2 -0
  156. zebra_day/web/routers/api.py +163 -0
  157. zebra_day/web/routers/ui.py +1051 -0
  158. zebra_day/zpl_renderer.py +273 -0
  159. zebra_day-1.0.2.dist-info/METADATA +786 -0
  160. zebra_day-1.0.2.dist-info/RECORD +179 -0
  161. {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/WHEEL +1 -1
  162. zebra_day-1.0.2.dist-info/entry_points.txt +4 -0
  163. zebra_day/etc/.blind +0 -1
  164. zebra_day/etc/current_style.txt +0 -1
  165. zebra_day/etc/label_styles/test_2inX1in.zpl +0 -22
  166. zebra_day/etc/label_styles/tmps/labware_2inX1in.na.2023-10-24_12:49:21.045127.zpl +0 -21
  167. zebra_day/etc/label_styles/tmps/labware_2inX1in.naggg.2023-10-24_16:02:04.704814.8888.2023-10-24_16:02:16.443911.zpl +0 -21
  168. zebra_day/etc/label_styles/tmps/labware_2inX1in.naggg.2023-10-24_16:02:04.704814.zpl +0 -21
  169. zebra_day/etc/label_styles/tmps/test_2inX1in.na.2023-10-24_12:45:37.002774.zpl +0 -22
  170. zebra_day/etc/old_printer_config/2023-10-24_16:06:06.931764_printer_config.json +0 -67
  171. zebra_day/etc/printer_config.json~ +0 -67
  172. zebra_day/files/tmp_2olihg4.png +0 -0
  173. zebra_day/files/tmpveojoyvn.png +0 -0
  174. zebra_day/files/zpl_label_labware_2inX1in_2023-10-25_02:30:08.093631.png +0 -0
  175. zebra_day/files/zpl_label_test_2inX1in_2023-10-24_15:54:29.343124.png +0 -0
  176. zebra_day/files/zpl_label_test_2inX1in_2023-10-24_16:01:45.670132.png +0 -0
  177. zebra_day/static/beyonce.css +0 -227
  178. zebra_day/static/datschund_on_moon.css +0 -164
  179. zebra_day/static/medicalsci.css +0 -144
  180. zebra_day/static/moar_zebra.css +0 -133
  181. zebra_day/static/popday.css +0 -140
  182. zebra_day/static/popday_dark.css +0 -140
  183. zebra_day/static/popday_dog.css +0 -140
  184. zebra_day-0.0.32.dist-info/METADATA +0 -14
  185. zebra_day-0.0.32.dist-info/RECORD +0 -53
  186. zebra_day-0.0.32.dist-info/entry_points.txt +0 -2
  187. /zebra_day/{etc/label_styles/blank_0inX0in.zpl → bin/__init__.py} +0 -0
  188. /zebra_day/etc/label_styles/{labware_2inX1in.zpl → generic_2inX1in.zpl} +0 -0
  189. {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info/licenses}/LICENSE +0 -0
  190. {zebra_day-0.0.32.dist-info → zebra_day-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,159 @@
1
+ """
2
+ Middleware for the zebra_day FastAPI application.
3
+
4
+ Provides request logging and rate limiting functionality.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import time
10
+ from collections import defaultdict
11
+ from typing import Callable
12
+
13
+ from fastapi import Request, Response
14
+ from starlette.middleware.base import BaseHTTPMiddleware
15
+
16
+ from zebra_day.logging_config import get_logger
17
+
18
+ _log = get_logger(__name__)
19
+
20
+
21
+ class RequestLoggingMiddleware(BaseHTTPMiddleware):
22
+ """
23
+ Middleware for structured request logging.
24
+
25
+ Logs client IP, request path, method, timing, and response status.
26
+ """
27
+
28
+ async def dispatch(self, request: Request, call_next: Callable) -> Response:
29
+ """Process request and log structured data."""
30
+ start_time = time.perf_counter()
31
+
32
+ # Extract client info
33
+ client_ip = request.client.host if request.client else "unknown"
34
+ method = request.method
35
+ path = request.url.path
36
+ query = str(request.query_params) if request.query_params else ""
37
+
38
+ # Extract relevant parameters for print operations
39
+ lab = request.query_params.get("lab", "")
40
+ printer = request.query_params.get("printer", "")
41
+ template = request.query_params.get("label_zpl_style", "")
42
+
43
+ try:
44
+ response = await call_next(request)
45
+ status_code = response.status_code
46
+ outcome = "success" if status_code < 400 else "error"
47
+ except Exception as exc:
48
+ status_code = 500
49
+ outcome = "exception"
50
+ _log.exception(
51
+ "Request failed",
52
+ extra={
53
+ "client_ip": client_ip,
54
+ "method": method,
55
+ "path": path,
56
+ "error": str(exc),
57
+ },
58
+ )
59
+ raise
60
+
61
+ elapsed_ms = (time.perf_counter() - start_time) * 1000
62
+
63
+ # Build log context
64
+ log_context = {
65
+ "client_ip": client_ip,
66
+ "method": method,
67
+ "path": path,
68
+ "status_code": status_code,
69
+ "elapsed_ms": round(elapsed_ms, 2),
70
+ "outcome": outcome,
71
+ }
72
+
73
+ # Add print-specific context if relevant
74
+ if lab:
75
+ log_context["lab"] = lab
76
+ if printer:
77
+ log_context["printer"] = printer
78
+ if template:
79
+ log_context["template"] = template
80
+
81
+ # Log at appropriate level
82
+ if status_code >= 500:
83
+ _log.error("Request completed", extra=log_context)
84
+ elif status_code >= 400:
85
+ _log.warning("Request completed", extra=log_context)
86
+ else:
87
+ _log.info("Request completed", extra=log_context)
88
+
89
+ return response
90
+
91
+
92
+ class PrintRateLimiter:
93
+ """
94
+ Simple rate limiter for print endpoints.
95
+
96
+ Uses a sliding window approach with configurable limits.
97
+ """
98
+
99
+ def __init__(
100
+ self,
101
+ max_requests: int = 10,
102
+ window_seconds: float = 60.0,
103
+ max_concurrent: int = 3,
104
+ ):
105
+ """
106
+ Initialize rate limiter.
107
+
108
+ Args:
109
+ max_requests: Maximum requests per window per client IP
110
+ window_seconds: Time window in seconds
111
+ max_concurrent: Maximum concurrent print operations
112
+ """
113
+ self.max_requests = max_requests
114
+ self.window_seconds = window_seconds
115
+ self.max_concurrent = max_concurrent
116
+
117
+ self._request_times: dict[str, list[float]] = defaultdict(list)
118
+ self._semaphore = asyncio.Semaphore(max_concurrent)
119
+ self._lock = asyncio.Lock()
120
+
121
+ async def acquire(self, client_ip: str) -> tuple[bool, str]:
122
+ """
123
+ Try to acquire a print slot.
124
+
125
+ Returns:
126
+ Tuple of (allowed, reason)
127
+ """
128
+ now = time.time()
129
+
130
+ async with self._lock:
131
+ # Clean old entries
132
+ cutoff = now - self.window_seconds
133
+ self._request_times[client_ip] = [
134
+ t for t in self._request_times[client_ip] if t > cutoff
135
+ ]
136
+
137
+ # Check rate limit
138
+ if len(self._request_times[client_ip]) >= self.max_requests:
139
+ return False, f"Rate limit exceeded: {self.max_requests} requests per {self.window_seconds}s"
140
+
141
+ # Try to acquire semaphore (non-blocking check)
142
+ if self._semaphore.locked() and self._semaphore._value == 0:
143
+ return False, f"Too many concurrent print operations (max {self.max_concurrent})"
144
+
145
+ # Record this request
146
+ self._request_times[client_ip].append(now)
147
+
148
+ # Acquire semaphore for actual operation
149
+ await self._semaphore.acquire()
150
+ return True, ""
151
+
152
+ def release(self) -> None:
153
+ """Release a print slot after operation completes."""
154
+ self._semaphore.release()
155
+
156
+
157
+ # Global rate limiter instance
158
+ print_rate_limiter = PrintRateLimiter()
159
+
@@ -0,0 +1,2 @@
1
+ """FastAPI routers for zebra_day web application."""
2
+
@@ -0,0 +1,163 @@
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."""
43
+ name: str
44
+ ip_address: str
45
+ model: str
46
+ serial: str
47
+ label_zpl_styles: List[str]
48
+ print_method: str
49
+
50
+
51
+ class LabPrinters(BaseModel):
52
+ """Lab and its printers."""
53
+ lab: str
54
+ printers: List[PrinterInfo]
55
+
56
+
57
+ # ----- Endpoints -----
58
+
59
+ @router.get("/labs", response_model=List[str])
60
+ async def list_labs(request: Request) -> List[str]:
61
+ """List all available labs."""
62
+ zp = request.app.state.zp
63
+ return list(zp.printers.get("labs", {}).keys())
64
+
65
+
66
+ @router.get("/labs/{lab}/printers", response_model=List[PrinterInfo])
67
+ async def list_printers(request: Request, lab: str) -> List[PrinterInfo]:
68
+ """List all printers in a lab."""
69
+ zp = request.app.state.zp
70
+ labs = zp.printers.get("labs", {})
71
+
72
+ if lab not in labs:
73
+ raise HTTPException(status_code=404, detail=f"Lab '{lab}' not found")
74
+
75
+ printers = []
76
+ for name, info in labs[lab].items():
77
+ printers.append(
78
+ PrinterInfo(
79
+ name=name,
80
+ ip_address=info.get("ip_address", ""),
81
+ model=info.get("model", ""),
82
+ serial=info.get("serial", ""),
83
+ label_zpl_styles=info.get("label_zpl_styles", []),
84
+ print_method=info.get("print_method", ""),
85
+ )
86
+ )
87
+ return printers
88
+
89
+
90
+ @router.get("/templates", response_model=List[str])
91
+ async def list_templates(request: Request) -> List[str]:
92
+ """List all available ZPL templates."""
93
+ from pathlib import Path
94
+
95
+ pkg_path = request.app.state.pkg_path
96
+ styles_dir = pkg_path / "etc" / "label_styles"
97
+
98
+ templates = []
99
+ if styles_dir.exists():
100
+ for f in styles_dir.iterdir():
101
+ if f.is_file() and f.suffix == ".zpl":
102
+ templates.append(f.stem)
103
+
104
+ # Also include drafts
105
+ tmps_dir = styles_dir / "tmps"
106
+ if tmps_dir.exists():
107
+ for f in tmps_dir.iterdir():
108
+ if f.is_file() and f.suffix == ".zpl":
109
+ templates.append(f.stem)
110
+
111
+ return sorted(templates)
112
+
113
+
114
+ @router.post("/print", response_model=PrintResponse)
115
+ async def print_label(request: Request, print_req: PrintRequest) -> PrintResponse:
116
+ """Send a print request to a printer."""
117
+ zp = request.app.state.zp
118
+ rate_limiter = request.app.state.print_rate_limiter
119
+ client_ip = request.client.host if request.client else "unknown"
120
+
121
+ # Check rate limit
122
+ allowed, reason = await rate_limiter.acquire(client_ip)
123
+ if not allowed:
124
+ raise HTTPException(status_code=429, detail=reason)
125
+
126
+ try:
127
+ result = zp.print_zpl(
128
+ lab=print_req.lab,
129
+ printer_name=print_req.printer,
130
+ label_zpl_style=print_req.label_zpl_style,
131
+ uid_barcode=print_req.uid_barcode,
132
+ alt_a=print_req.alt_a,
133
+ alt_b=print_req.alt_b,
134
+ alt_c=print_req.alt_c,
135
+ alt_d=print_req.alt_d,
136
+ alt_e=print_req.alt_e,
137
+ alt_f=print_req.alt_f,
138
+ print_n=print_req.copies,
139
+ client_ip=client_ip,
140
+ )
141
+
142
+ # Check if result is a PNG file path
143
+ if result and ".png" in str(result):
144
+ png_name = str(result).split("/")[-1]
145
+ return PrintResponse(
146
+ success=True,
147
+ message="PNG generated successfully",
148
+ png_url=f"/files/{png_name}",
149
+ )
150
+
151
+ return PrintResponse(success=True, message="Print request sent successfully")
152
+ except Exception as e:
153
+ raise HTTPException(status_code=500, detail=str(e))
154
+ finally:
155
+ rate_limiter.release()
156
+
157
+
158
+ @router.get("/config")
159
+ async def get_config(request: Request) -> Dict[str, Any]:
160
+ """Get the current printer configuration."""
161
+ zp = request.app.state.zp
162
+ return zp.printers
163
+