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
zebra_day/print_mgr.py CHANGED
@@ -1,17 +1,55 @@
1
- import os
2
- import sys
3
- import socket
1
+ """
2
+ Primary zebra_day module. Primary functions: consistent and clear management
3
+ of 1+ networked zebra printers, automated discovery of printers on a
4
+ network. Clear formulation and delivery of ZPL strings to destination
5
+ printers. Management of zpl template files, which may have format value
6
+ components for inserting data on the fly. (elsewhere, a simple ui on
7
+ top of this).
8
+
9
+ This module is primarily focused on print request and package config mgmt.
10
+ See 'cmd_mgr' for interacting with zebras printer config capabilities.
11
+ """
12
+ from __future__ import annotations
13
+
4
14
  import datetime
5
15
  import json
6
- import requests
16
+ import os
17
+ import shutil
18
+ import socket
19
+ import subprocess
20
+ import sys
21
+ import time
22
+ from pathlib import Path
23
+
24
+ from importlib.resources import files
25
+
26
+ from zebra_day.logging_config import get_logger
27
+ from zebra_day import paths as xdg
28
+
29
+ _log = get_logger(__name__)
30
+
7
31
 
8
32
 
9
33
  def get_current_date():
34
+ """
35
+ get the current datetime
36
+ """
37
+
10
38
  current_date = datetime.date.today()
11
39
  formatted_date = current_date.strftime("%Y-%m-%d")
12
40
  return formatted_date
13
41
 
14
- def send_zpl_code(zpl_code, printer_ip, printer_port=9100):
42
+
43
+ def send_zpl_code(zpl_code, printer_ip, printer_port=9100, is_test=False):
44
+ """
45
+ The bit which passes the zpl to the specified printer.
46
+ Port is more or less hard coded upstream from here fwiw
47
+ """
48
+
49
+ # In the case we are testing only, return None
50
+ if is_test:
51
+ return None
52
+
15
53
  # Create a socket object
16
54
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
17
55
  timeout = 5
@@ -22,82 +60,271 @@ def send_zpl_code(zpl_code, printer_ip, printer_port=9100):
22
60
  sock.connect((printer_ip, printer_port))
23
61
 
24
62
  # Send the ZPL code as raw bytes
25
- sock.sendall(zpl_code.encode())
26
- print("ZPL code sent successfully to the printer!")
27
-
63
+ # ... the zebra printer will not throw an error if the request
64
+ # content is incorrect, or for any reason except to reject request to the wrong port.
65
+ return_code = sock.sendall(zpl_code.encode())
66
+ if return_code in [None]:
67
+ _log.info("ZPL code sent successfully to printer %s:%d", printer_ip, printer_port)
68
+ else:
69
+ raise Exception(f"\n\nPrint request to {printer_ip}:{printer_port} did not return None, but instead: {return_code} ... zpl: {zpl_code}\n")
70
+
28
71
  except ConnectionError as e:
29
- print(f"Error connecting to the printer: {e}")
72
+ raise Exception(f"Error connecting to the printer: {printer_ip} on port {printer_port} \n\n\t"+str(e))
30
73
 
31
74
  finally:
32
75
  # Close the socket connection
33
76
  sock.close()
34
77
 
35
- # Due to some bizarre packaging thing I havent been able to puzzle out, when pip installed from pypy, included files are not found and you need to fetch this objec via:
36
- ## import zebra_day.print_mgr as zdpm
37
- ## z=zdpm.zplo()
78
+ """
79
+ The zpl.printers object is critical part of zebra_day. There is an in memory js on which can be stored to an active use json file. This active use file is
80
+ used when creating a new zpl() class. If absent, a minimal viable json
81
+ object is created in memory, which needs to be populated (via a few methods
82
+ below, or manually if you'd like) before you can do very much.
83
+
84
+
85
+
86
+ """
87
+
38
88
 
39
89
  class zpl:
90
+ """
91
+ The primary class. Instantiate with:
92
+ from zebra_day import print_mgr as zd
93
+ zd_pm = zd.zpl()
94
+ """
95
+
96
+ def __init__(self, json_config: str | None = None):
97
+ """
98
+ Initialize the class.
99
+
100
+ Args:
101
+ json_config: Path to printer config JSON. If not specified,
102
+ uses XDG config path or falls back to package path.
103
+ """
104
+ # Ensure label styles directories exist
105
+ xdg.get_label_drafts_dir() # Creates tmps/ too
106
+
107
+ # Determine config file location (XDG first, then package fallback)
108
+ xdg_config = xdg.get_printer_config_path()
109
+ pkg_config = Path(str(files('zebra_day'))) / "etc" / "printer_config.json"
110
+
111
+ if json_config:
112
+ jcfg = Path(json_config) if not json_config.startswith('/') else Path(json_config)
113
+ elif xdg_config.exists():
114
+ jcfg = xdg_config
115
+ elif pkg_config.exists():
116
+ jcfg = pkg_config
117
+ else:
118
+ jcfg = xdg_config # Will create new config here
119
+
120
+ if jcfg.exists():
121
+ self.load_printer_json(str(jcfg), relative=False)
122
+ else:
123
+ self.create_new_printers_json_with_single_test_printer(str(jcfg))
40
124
 
41
- def __init__(self, debug=0,json_config='zebra_day/etc/printer_config.json'):
42
- self.load_printer_json(json_config)
43
- self.debug = False if debug in [0,'0'] else True
44
125
 
126
+ def probe_zebra_printers_add_to_printers_json(self, ip_stub="192.168.1", scan_wait="0.25",lab="scan-results", relative=False):
127
+ """
128
+ Scan the network for zebra printers
129
+ NOTE! this should work with no dependencies on a MAC
130
+ UBUNTU requires system wide net-tools (for arp)
131
+ Others... well, this may not work
45
132
 
46
- def probe_zebra_printers_add_to_printers_json(self, ip_stub="192.168.1", scan_wait="0.25",lab="scan-results"):
133
+ ---
134
+ Requires:
135
+ curl is pretty standard, arp seems less so
136
+ arp
137
+ ---
138
+
139
+ ip_stub = all 255 possibilities will be probed beneath this
140
+ stub provided
141
+
142
+ can_wait = seconds to re-try probing until moving on. 0.25
143
+ default may be too squick
144
+
145
+ lab = code for the lab key to add/update to given finding
146
+ new printers. Existing printers will be over written.
147
+ """
47
148
 
48
149
  if lab not in self.printers['labs']:
49
150
  self.printers['labs'][lab] = {}
50
151
 
51
- self.printers['labs'][lab]["Download-Label-png"] = { "ip_address": "dl_png", "label_zpl_styles": ["test_2inX1in"],"print_method": "generate png", "model" : "na", "serial" : "na"}
52
-
53
- res = os.popen(f"zebra_day/bin/scan_for_networed_zebra_printers_curl.sh {ip_stub} {scan_wait}")
54
- for i in res.readlines():
55
- ii = i.rstrip()
56
- sl = ii.split('|')
152
+ self.printers['labs'][lab]["Download-Label-png"] = {
153
+ "ip_address": "dl_png",
154
+ "label_zpl_styles": ["tube_2inX1in"],
155
+ "print_method": "generate png",
156
+ "model": "na",
157
+ "serial": "na",
158
+ "arp_data": ""
159
+ }
160
+
161
+ # Run scanner script using subprocess instead of os.popen
162
+ script_path = Path(str(files('zebra_day'))) / "bin" / "scan_for_networed_zebra_printers_curl.sh"
163
+ result = subprocess.run(
164
+ [str(script_path), ip_stub, scan_wait],
165
+ capture_output=True,
166
+ text=True,
167
+ check=False
168
+ )
169
+
170
+ for line in result.stdout.splitlines():
171
+ line = line.rstrip()
172
+ sl = line.split('|')
57
173
  if len(sl) > 1:
58
174
  zp = sl[0]
59
175
  ip = sl[1]
60
176
  model = sl[2]
61
177
  serial = sl[3]
62
178
  status = sl[4]
63
- if ip not in self.printers['labs'][lab]:
64
- self.printers['labs'][lab][ip] = {"ip_address" : ip, "label_zpl_styles" : ["blank_0inX0in", "test_2inX1in","tube_2inX1in", "plate_1inX0.25in", "tube_2inX0.3in"], "print_method" : "unk", "model" : model, "serial" : serial} # The label formats set here are the installed defaults
179
+ arp_response = sl[5]
65
180
 
66
- self.save_printer_json()
67
-
68
-
69
- # USING SELF.PRINTERS
70
- def save_printer_json(self, json_filename="zebra_day/etc/printer_config.json"):
71
- rec_date = str(datetime.datetime.now()).replace(' ','_')
72
- bkup_pconfig_fn = f"zebra_day/etc/old_printer_config/{rec_date}_printer_config.json"
181
+ if ip not in self.printers['labs'][lab]:
182
+ # The label formats set here are the installed defaults
183
+ self.printers['labs'][lab][ip] = {
184
+ "ip_address": ip,
185
+ "label_zpl_styles": ["tube_2inX1in", "plate_1inX0.25in", "tube_2inX0.3in"],
186
+ "print_method": "unk",
187
+ "model": model,
188
+ "serial": serial,
189
+ "arp_data": arp_response
190
+ }
191
+
192
+ self.save_printer_json(self.printers_filename, relative=False)
193
+
194
+
195
+ def save_printer_json(self, json_filename: str = "/etc/printer_config.json", relative: bool = True) -> None:
196
+ """
197
+ Save the current self.printers to the specified JSON file.
198
+
199
+ Creates a backup of the previous config in the backups directory.
200
+
201
+ Args:
202
+ json_filename: Path to save the config to
203
+ relative: If True, path is relative to package directory
204
+ """
205
+ # Resolve the target path
206
+ if relative:
207
+ json_path = Path(str(files('zebra_day'))) / json_filename.lstrip('/')
208
+ else:
209
+ json_path = Path(json_filename)
210
+
211
+ # Create backup if file exists
212
+ if hasattr(self, 'printers_filename') and Path(self.printers_filename).exists():
213
+ backup_dir = xdg.get_config_backups_dir()
214
+ rec_date = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
215
+ backup_path = backup_dir / f"{rec_date}_printer_config.json"
216
+ try:
217
+ shutil.copy2(self.printers_filename, backup_path)
218
+ _log.debug("Backup created: %s", backup_path)
219
+ except OSError as e:
220
+ _log.warning("Failed to create backup: %s", e)
221
+
222
+ # Save the config
223
+ json_path.parent.mkdir(parents=True, exist_ok=True)
224
+ with open(json_path, 'w') as json_file:
225
+ json.dump(self.printers, json_file, indent=4)
73
226
 
74
- os.system(f"cp {json_filename} {bkup_pconfig_fn}")
227
+ self.load_printer_json(str(json_path), relative=False)
75
228
 
76
- with open(json_filename, 'w') as json_file:
77
- json.dump(self.printers, json_file, indent=4)
78
- self.load_printer_json(json_filename)
79
229
 
230
+ def load_printer_json(self, json_file=f"etc/printer_config.json", relative=True):
231
+ """
232
+ Loads printer json from a specified file, saves it to the active json.
233
+ If specified file does not exist, it is created with the base
234
+ printers json
235
+
236
+ json_file = path to file
237
+ """
238
+ if relative:
239
+ json_file = f"{str(files('zebra_day'))}/{json_file}"
240
+ else:
241
+ pass
242
+
243
+ _log.debug("Loading printer config from: %s", json_file)
80
244
 
81
- def load_printer_json(self, json_file="zebra_day/etc/printer_config.json"):
82
245
  if not os.path.exists(json_file):
83
- os.system("""echo '{ "labs" : {} }' >>""" + json_file)
246
+ raise Exception(f"""The file specified does not exist. Consider specifying the default 'etc/printer_config.json , provided: {json_file}, which had {str(files('zebra_day'))} prefixed to it', for {json_file}""")
84
247
  fh = open(json_file)
85
248
  self.printers_filename = json_file
86
249
  self.printers = json.load(fh)
250
+ # self.save_printer_json() <--- use the save_printer_json call after calling this. Else, recursion.
251
+
87
252
 
253
+ def create_new_printers_json_with_single_test_printer(self, fn=None):
254
+ """
255
+ Create a new printers json with just the png printer defined
256
+ """
88
257
 
89
- def clear_printers_json(self, json_file="zebra_day/etc/printer_config.json"):
90
- os.system(f"""echo '{{"labs" : {{}} }}' > {json_file}""")
91
- fh = open(json_file)
92
- self.printers_filename = json_file
93
- self.printers = json.load(fh)
94
258
 
259
+ if fn in [None]:
260
+ fn = str(files('zebra_day'))+"/etc/printer_config.json"
261
+
262
+ if not hasattr(self, 'printers'):
263
+ self.printers = {}
264
+ self.printers_filename = fn
265
+
266
+ jdat = None
267
+ with open(f"{str(files('zebra_day'))}/etc/printer_config.template.json", 'r') as file:
268
+ jdat = json.load(file)
269
+
270
+ self.printers = jdat
271
+
272
+ self.save_printer_json(fn, relative=False)
273
+
274
+
275
+ def clear_printers_json(self, json_file: str = "/etc/printer_config.json") -> None:
276
+ """
277
+ Reset printers JSON to empty minimal structure.
278
+
279
+ Args:
280
+ json_file: Path to the config file (relative to package)
281
+ """
282
+ json_path = Path(str(files('zebra_day'))) / json_file.lstrip('/')
283
+
284
+ # Write empty config using pathlib
285
+ empty_config = {"labs": {}}
286
+ json_path.parent.mkdir(parents=True, exist_ok=True)
287
+ with open(json_path, 'w') as f:
288
+ json.dump(empty_config, f, indent=4)
289
+
290
+ self.printers_filename = str(json_path)
291
+ self.printers = empty_config
292
+
293
+ self.save_printer_json(str(json_path), relative=False)
294
+
295
+ def replace_printer_json_from_template(self) -> None:
296
+ """
297
+ Replace the active printer config with the default template.
298
+
299
+ Copies the template JSON to the active config location.
300
+ """
301
+ pkg_path = Path(str(files('zebra_day')))
302
+ template_path = pkg_path / "etc" / "printer_config.template.json"
303
+ target_path = pkg_path / "etc" / "printer_config.json"
304
+
305
+ # Copy template to active config using shutil
306
+ shutil.copy2(template_path, target_path)
307
+
308
+ with open(target_path) as fh:
309
+ self.printers = json.load(fh)
310
+ self.printers_filename = str(target_path)
311
+
312
+ self.save_printer_json(self.printers_filename, relative=False)
95
313
 
96
- def replace_printer_json_from_template(self):
97
- os.system('cp zebra_day/etc/printer_config.template.json zebra_day/etc/printer_config.json')
98
314
 
99
315
 
100
316
  def get_valid_label_styles_for_lab(self,lab=None):
317
+ """
318
+ The intention for this method was to confirm a template
319
+ being requested for use in printing to some printer
320
+ was 'allowed' by checking with that printers printer json
321
+ for the array of valid templates.
322
+
323
+ This was a huge PITA in testing, could be re-enabled at some point
324
+
325
+ It is used once, but prints a warning only.
326
+ """
327
+
101
328
  unique_labels = set()
102
329
 
103
330
  for printer in self.printers['labs'][lab]:
@@ -108,11 +335,25 @@ class zpl:
108
335
  return result
109
336
 
110
337
 
338
+ # Given these inputs, format them in to the specified zpl template and
339
+ # prepare a string to send to a printer
111
340
  def formulate_zpl(self,uid_barcode=None, alt_a=None, alt_b=None, alt_c=None, alt_d=None, alt_e=None, alt_f=None, label_zpl_style=None):
112
-
113
- zpl_file = f"zebra_day/etc/label_styles/{label_zpl_style}.zpl"
341
+ """
342
+ Produce a ZPL string using the specified zpl template file, and
343
+ formatting in the values, where appropriate.
344
+
345
+ label_zpl_style = filename, minus the .zpl which keys to the .zpl file.
346
+ (note, NOT the full file name. This shoudlbe changed
347
+ to full file paths at some point)
348
+
349
+ uid_barcode and alt_a -to- alt_f, are the allowed format keys in
350
+ the zpl templates. They may be used in any way. uid_barcode
351
+ just differntiates one.
352
+ """
353
+
354
+ zpl_file = str(files('zebra_day'))+f"/etc/label_styles/{label_zpl_style}.zpl"
114
355
  if not os.path.exists(zpl_file):
115
- zpl_file = f"zebra_day/etc/label_styles/tmps/{label_zpl_style}.zpl"
356
+ zpl_file = str(files('zebra_day'))+f"/etc/label_styles/tmps/{label_zpl_style}.zpl"
116
357
  if not os.path.exists(zpl_file):
117
358
  raise Exception(f"ZPL File : {zpl_file} does not exist in the TOPLEVEL or TMPS zebra_day/etc/label_styles dir.")
118
359
 
@@ -123,48 +364,102 @@ class zpl:
123
364
  return zpl_string
124
365
 
125
366
 
126
- def generate_label_png(self,zpl_string=None, png_fn=None):
127
-
128
- if zpl_string in [None] or png_fn in [None]:
129
- raise Exception('ERROR: zpl_string and png_fn may not be None.')
130
-
131
- # Labelary API URL
132
- labelary_url = "http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/"
367
+
368
+ def generate_label_png(self, zpl_string=None, png_fn=None, relative=False):
369
+ """
370
+ Generate a PNG image from ZPL string using local renderer.
371
+
372
+ This uses a local ZPL renderer (Pillow + zint-bindings) instead of
373
+ the external Labelary API, enabling offline operation and avoiding
374
+ rate limits.
375
+
376
+ Args:
377
+ zpl_string: The ZPL code to render
378
+ png_fn: Output filename for the PNG
379
+ relative: If True, treat png_fn as relative to package directory
380
+
381
+ Returns:
382
+ Path to the generated PNG file
383
+ """
384
+ from zebra_day.zpl_renderer import render_zpl_to_png
385
+
386
+ if relative:
387
+ png_fn = str(files('zebra_day')) + '/' + png_fn
388
+
389
+ if zpl_string is None or png_fn is None:
390
+ raise ValueError('ERROR: zpl_string and png_fn may not be None.')
391
+
392
+ try:
393
+ result = render_zpl_to_png(zpl_string, png_fn)
394
+ _log.info("Label image saved as %s", result)
395
+ return result
396
+ except Exception as e:
397
+ _log.error("Failed to convert ZPL to image: %s", e)
398
+ raise
399
+
400
+
401
+ def print_raw_zpl(self,zpl_content,printer_ip, port=9100):
402
+ """
403
+ For use when no use of the printer mapping config json is needed. This assumes you know which IP is your desired printer. The spcified zpl_content will be sent to that IP+port.
404
+ """
405
+ send_zpl_code(zpl_content, printer_ip, printer_port=port)
406
+
407
+
408
+
409
+
410
+ def print_zpl(self, lab=None, printer_name=None, uid_barcode='', alt_a='', alt_b='', alt_c='', alt_d='', alt_e='', alt_f='', label_zpl_style=None, client_ip='pkg', print_n=1, zpl_content=None):
411
+ """
412
+ The main print method. Accepts info to determine the desired
413
+ printer IP and to request the desired ZPL string to be sent
414
+ to the printer.
415
+
416
+ lab = top level key in self.printers['labs']
417
+ printer_name = key for printer info (ie: ip_address) needed
418
+ to satisfy print requests.
419
+ label_zpl_style = template code, see above for addl deets
420
+ client_ip = optional, this is logged with print request info
421
+ print_n = integer, > 0
422
+ zpl_content = DO NOT USE -- hacky way to directly pass a zpl
423
+ string to a printer. to do: write a cleaner
424
+ string+ip method of printing.
425
+ """
426
+
427
+ if print_n < 1:
428
+ raise Exception(f"\n\nprint_n < 1 , specified {print_n}")
133
429
 
134
- # Create a POST request to the Labelary API
135
- response = requests.post(labelary_url, data=zpl_string)
136
-
137
- # Check if the request was successful
138
- if response.status_code == 200:
139
- # Save the image to a file
140
- with open(png_fn, "wb") as f:
141
- f.write(response.content)
142
- print(f"Image saved as {png_fn}")
143
- else:
144
- print(f"Failed to convert ZPL to image. Status code: {response.status_code}")
145
-
146
- return png_fn
147
-
148
-
149
- def print_zpl(self, lab=None, printer_name=None, uid_barcode='', alt_a='', alt_b='', alt_c='', alt_d='', alt_e='', alt_f='', label_zpl_style=None, client_ip='pkg', print_n=1):
150
430
  rec_date = str(datetime.datetime.now()).replace(' ','_')
151
431
  print_n = int(print_n)
152
432
 
433
+ if printer_name in ['','None',None] and lab in [None,'','None']:
434
+ raise Exception(f"lab and printer_name are both required to route a zebra print request, the following was what was received: lab:{lab} & printer_name:{printer_name}")
435
+
153
436
  if label_zpl_style in [None,'','None']:
154
437
  label_zpl_style = self.printers['labs'][lab][printer_name]['label_zpl_styles'][0] # If a style is not specified, assume the first
155
438
  elif label_zpl_style not in self.printers['labs'][lab][printer_name]['label_zpl_styles']:
156
- print(f"\n\nWARNING:::\nZPL style: {label_zpl_style} is not valid for {lab} {printer_name} ... {self.printers['labs'][lab][printer_name]['label_zpl_styles']}")
439
+ _log.warning(
440
+ "ZPL style '%s' is not valid for %s/%s. Valid styles: %s",
441
+ label_zpl_style, lab, printer_name,
442
+ self.printers['labs'][lab][printer_name]['label_zpl_styles']
443
+ )
157
444
 
158
445
  printer_ip = self.printers['labs'][lab][printer_name]["ip_address"]
159
446
 
160
- zpl_string = self.formulate_zpl(uid_barcode=uid_barcode, alt_a=alt_a, alt_b=alt_b, alt_c=alt_c, alt_d=alt_d, alt_e=alt_e, alt_f=alt_f, label_zpl_style=label_zpl_style)
447
+ zpl_string = ''
448
+ if zpl_content in [None]:
449
+ zpl_string = self.formulate_zpl(uid_barcode=uid_barcode, alt_a=alt_a, alt_b=alt_b, alt_c=alt_c, alt_d=alt_d, alt_e=alt_e, alt_f=alt_f, label_zpl_style=label_zpl_style)
450
+ else:
451
+ zpl_string = zpl_content
161
452
 
162
- os.system(f"echo '{lab}\t{printer_name}\t{uid_barcode}\t{label_zpl_style}\t{printer_ip}\t{print_n}\t{client_ip}' >> zebra_day/logs/print_requests.log")
453
+ # Log print request to file (using pathlib, not shell)
454
+ log_file = xdg.get_logs_dir() / "print_requests.log"
455
+ log_entry = f"{lab}\t{printer_name}\t{uid_barcode}\t{label_zpl_style}\t{printer_ip}\t{print_n}\t{client_ip}\t{zpl_content}\n"
456
+ with open(log_file, 'a') as f:
457
+ f.write(log_entry)
163
458
 
164
459
  ret_s = None
165
460
  if printer_ip in ['dl_png']:
166
- png_fn = f"zebra_day/files/zpl_label_{label_zpl_style}_{rec_date}.png"
167
- ret_s = self.generate_label_png(zpl_string, png_fn)
461
+ png_fn = str(xdg.get_generated_files_dir() / f"zpl_label_{label_zpl_style}_{rec_date}.png")
462
+ ret_s = self.generate_label_png(zpl_string, png_fn, False)
168
463
 
169
464
  else:
170
465
  pn = 1
@@ -174,49 +469,140 @@ class zpl:
174
469
 
175
470
  ret_s = zpl_string
176
471
 
177
- if self.debug:
178
- print(f"\nZPL STRING :: {zpl_string}\n")
179
-
180
472
  return ret_s
181
473
 
182
474
 
183
- # Due to some bizarre packaging thing I havent been able to puzzle out, when pip installed from pypy, included files are not found and you need to fetch this objec via:
184
- ## import zebra_day.print_mgr as zdpm
185
- ## z=zdpm.zplo()
186
- def zplo():
475
+ def _get_local_ip() -> str:
476
+ """Get the local IP address of this machine."""
477
+ ipcmd = r"""(ip addr show | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' || ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') 2>/dev/null"""
478
+ result = subprocess.run(ipcmd, shell=True, capture_output=True, text=True)
479
+ return result.stdout.strip().split('\n')[0] if result.stdout.strip() else "127.0.0.1"
480
+
481
+
482
+ def _parse_auth_args() -> str:
483
+ """Parse --auth CLI argument.
484
+
485
+ Returns:
486
+ Auth mode: "none" or "cognito"
487
+ """
488
+ import argparse
489
+
490
+ parser = argparse.ArgumentParser(add_help=False)
491
+ parser.add_argument(
492
+ "--auth",
493
+ type=str,
494
+ choices=["none", "cognito"],
495
+ default="none",
496
+ help="Authentication mode: 'none' (public, default) or 'cognito' (AWS Cognito)",
497
+ )
498
+ args, _ = parser.parse_known_args()
499
+ return args.auth
500
+
501
+
502
+ def zday_start() -> None:
503
+ """
504
+ Start the zebra_day web UI on 0.0.0.0:8118.
505
+
506
+ .. deprecated::
507
+ Use ``zday gui start`` instead. This command will be removed in v1.0.
508
+
509
+ This offers package utilities in a UI, mostly intended for
510
+ template design, testing, and printer fleet maintenance.
511
+
512
+ Usage:
513
+ zday_start # Start with no authentication
514
+ zday_start --auth none # Explicit no authentication
515
+ zday_start --auth cognito # Enable Cognito authentication
516
+ """
517
+ import warnings
518
+ warnings.warn(
519
+ "zday_start is deprecated. Use 'zday gui start' instead.",
520
+ DeprecationWarning,
521
+ stacklevel=2,
522
+ )
523
+ _log.warning(
524
+ "DEPRECATED: zday_start is deprecated. Use 'zday gui start' instead."
525
+ )
526
+
527
+ from zebra_day.web.app import run_server
528
+
529
+ auth_mode = _parse_auth_args()
530
+ _log.info("Starting zebra_day FastAPI server on 0.0.0.0:8118 (auth=%s)...", auth_mode)
531
+ run_server(host="0.0.0.0", port=8118, reload=False, auth=auth_mode)
532
+
533
+
534
+ def main() -> None:
535
+ """
536
+ Quick start: scan for printers and start the web GUI.
537
+
538
+ .. deprecated::
539
+ Use ``zday bootstrap`` followed by ``zday gui start`` instead.
540
+ This command will be removed in v1.0.
541
+
542
+ If zebra_day has been pip installed, running zday_quickstart
543
+ will first attempt a zebra printer discovery scan of your network,
544
+ create a new printers JSON for what is found, and start
545
+ the zebra_day UI on 0.0.0.0:8118.
546
+
547
+ Usage:
548
+ zday_quickstart # Start with no authentication
549
+ zday_quickstart --auth none # Explicit no authentication
550
+ zday_quickstart --auth cognito # Enable Cognito authentication
551
+ """
552
+ import warnings
553
+ warnings.warn(
554
+ "zday_quickstart is deprecated. Use 'zday bootstrap' then 'zday gui start' instead.",
555
+ DeprecationWarning,
556
+ stacklevel=2,
557
+ )
558
+ _log.warning(
559
+ "DEPRECATED: zday_quickstart is deprecated. Use 'zday bootstrap' then 'zday gui start' instead."
560
+ )
561
+
187
562
  import zebra_day.print_mgr as zdpm
188
- os.chdir(os.path.dirname(zdpm.__file__))
189
- os.chdir('..')
190
- zp = zdpm.zpl()
191
- return zp
563
+ from zebra_day.web.app import run_server
192
564
 
565
+ auth_mode = _parse_auth_args()
193
566
 
194
- def main():
195
- ipcmd = "(ip addr show | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' || ifconfig | grep -Eo 'inet (addr:\
196
- )?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') 2>/dev/null"
197
- print(ipcmd)
198
- ip = os.popen(ipcmd).readline().rstrip()
567
+ ip = _get_local_ip()
199
568
  ip_root = ".".join(ip.split('.')[:-1])
200
569
 
201
- print(f"\nIP detected: {ip} ... using IP root: {ip_root}\n\n ..... now scanning for zebra printers on this network (which may take a few minutes...)")
202
- os.system('sleep 2.2')
203
- import zebra_day.print_mgr as zdpm
204
- os.chdir(os.path.dirname(zdpm.__file__))
205
- os.chdir('..')
570
+ _log.info("IP detected: %s ... using IP root: %s", ip, ip_root)
571
+ _log.info("Scanning for zebra printers on this network (may take a few minutes)...")
572
+ time.sleep(2.2)
573
+
206
574
  zp = zdpm.zpl()
207
575
  zp.probe_zebra_printers_add_to_printers_json(ip_stub=ip_root)
208
576
 
209
- print(f"\nZebra Printer Scan Complete. Results:" + str(zp.printers) + "\n\n")
210
- print(f'\nNow starting zebra_day web GUI\n\n\n\t\t\t**** THE ZDAY WEB GUI WILL BE ACCESSIBLE VIA THE URL: {ip}:8118 \n\n\n\tThe zday web server will continue running, and not return this shell to a command prompt until it is shut down\n\t.... you may shut down this web service by hitting ctrl+c.\n\n')
211
- os.system('sleep 1.3')
212
-
213
- os.system('python zebra_day/bin/zserve.py')
577
+ _log.info("Zebra Printer Scan Complete. Results: %s", zp.printers)
578
+ _log.info(
579
+ "Starting zebra_day web GUI at %s:8118 (auth=%s). "
580
+ "Press Ctrl+C to shut down.",
581
+ ip,
582
+ auth_mode,
583
+ )
584
+ time.sleep(1.3)
214
585
 
215
- print('\n\n\n ** EXITING ZDAY QUICKSTART **\n\n\t\tif the zday web gui did not run ( if you immediately got the command prompt back, it did not run ), check and see if there is a service already running at {ip}:8118 . Otherwise, check out the zday cherrypy STDOUT emitted just above what you are reading now. Cut&Paste that into chatgpt and see if a solution is presented!')
586
+ run_server(host="0.0.0.0", port=8118, reload=False, auth=auth_mode)
216
587
 
217
- print('fin')
588
+ _log.info("EXITING ZDAY QUICKSTART")
589
+ _log.info(
590
+ "If the web GUI did not run, check if a service is already running at %s:8118",
591
+ ip
592
+ )
218
593
 
219
594
 
220
595
  if __name__ == "__main__":
596
+ """
597
+ entry point for zday_quickstart.
598
+ """
221
599
 
222
600
  main()
601
+
602
+
603
+ if __name__ == "__zday_start__":
604
+ """
605
+ entry point for zday_start
606
+ """
607
+
608
+ zday_start()