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