pyxlsbwriter 0.0.1__tar.gz

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.
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyxlsbwriter
3
+ Version: 0.0.1
4
+ Summary: A Python library to write XLSB files.
5
+ Author: Krzysztof Duśko
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: xlsxwriter
12
+ Provides-Extra: test
13
+ Requires-Dist: pyodbc; extra == "test"
14
+ Provides-Extra: examples
15
+ Requires-Dist: memory-profiler; extra == "examples"
16
+
17
+ # Python XLSB Writer
18
+
19
+ A Python library for writing large data sets to XLSB files efficiently.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install pyxlsbwriter
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from pyxlsbwriter import XlsbWriter
31
+ import datetime
32
+ from decimal import Decimal
33
+
34
+ data = [
35
+ ["Name", "Age", "City", "info"],
36
+ [-123, 2147483647, 2147483648, 2147483999],
37
+ ["x", "y", "z", datetime.datetime.today()],
38
+ ["Alice", 25, "New York", datetime.date.today()],
39
+ ["Bob", 30, "London", Decimal(3.14)],
40
+ ["Charlie", 35, "Paris", datetime.datetime.now()],
41
+ [True, False, None, datetime.datetime.utcnow()]
42
+ ]
43
+
44
+ writer = XlsbWriter("test.xlsb")
45
+ writer.add_sheet("Sheet1")
46
+ writer.write_sheet(data)
47
+ writer.save()
48
+ ```
@@ -0,0 +1,32 @@
1
+ # Python XLSB Writer
2
+
3
+ A Python library for writing large data sets to XLSB files efficiently.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install pyxlsbwriter
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from pyxlsbwriter import XlsbWriter
15
+ import datetime
16
+ from decimal import Decimal
17
+
18
+ data = [
19
+ ["Name", "Age", "City", "info"],
20
+ [-123, 2147483647, 2147483648, 2147483999],
21
+ ["x", "y", "z", datetime.datetime.today()],
22
+ ["Alice", 25, "New York", datetime.date.today()],
23
+ ["Bob", 30, "London", Decimal(3.14)],
24
+ ["Charlie", 35, "Paris", datetime.datetime.now()],
25
+ [True, False, None, datetime.datetime.utcnow()]
26
+ ]
27
+
28
+ writer = XlsbWriter("test.xlsb")
29
+ writer.add_sheet("Sheet1")
30
+ writer.write_sheet(data)
31
+ writer.save()
32
+ ```
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pyxlsbwriter"
7
+ version = "0.0.1"
8
+ authors = [
9
+ { name="Krzysztof Duśko"},
10
+ ]
11
+ description = "A Python library to write XLSB files."
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+ dependencies = [
20
+ "xlsxwriter"
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ test = [
25
+ "pyodbc"
26
+ ]
27
+ examples = [
28
+ "memory-profiler"
29
+ ]
30
+
31
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .writer import XlsbWriter
2
+
3
+ __all__ = ['XlsbWriter']
@@ -0,0 +1,618 @@
1
+ from dataclasses import dataclass
2
+ import datetime
3
+ from decimal import Decimal
4
+ import zipfile
5
+ import struct
6
+ from typing import Tuple, Iterable
7
+ import io
8
+ import itertools
9
+
10
+ @dataclass
11
+ class FilterData:
12
+ sheet_index: int = 0
13
+ start_column: int = 0
14
+ end_column: int = 0
15
+ start_row: int = 0
16
+ end_row: int = 0
17
+
18
+
19
+ class XlsbWriter:
20
+ def __init__(self, filename: str):
21
+ self.filename = filename
22
+ self._worksheet_data: list[Tuple[str, Iterable[list[any]],bool]] = []
23
+ self._shared_strings: list[str] = []
24
+ self._shared_strings_dict: dict[str, int] = {}
25
+ self._sheet_count = 0
26
+ self._sst_unique_count = 0
27
+ self._sst_all_count = 0
28
+ self._filtered_data_list: list[FilterData] = []
29
+
30
+ def add_sheet(self, sheet_name: str):
31
+ self._sheet_count += 1
32
+ # Add a placeholder for the data iterable
33
+ self._worksheet_data.append((sheet_name, iter([])))
34
+
35
+ def write_sheet(self, data: Iterable[list[any]], hidden = False, compressionLevel:int = 4):
36
+ if not self._worksheet_data:
37
+ self.add_sheet("Sheet1")
38
+
39
+ # Store the iterable without consuming it. Processing is deferred to save().
40
+ sheet_name, _ = self._worksheet_data[self._sheet_count - 1]
41
+ self._worksheet_data[self._sheet_count - 1] = (sheet_name, data,hidden)
42
+
43
+ if self._zf is None:
44
+ self._zf = zipfile.ZipFile(self.filename, 'w', zipfile.ZIP_DEFLATED, compresslevel=compressionLevel)
45
+
46
+ for i, (sheet_name, sheet_data_iterable, _) in enumerate([self._worksheet_data[self._sheet_count - 1]]):
47
+ i = self._sheet_count - 1
48
+ sheet_id = i + 1
49
+ with self._zf.open(f"xl/worksheets/sheet{sheet_id}.bin", 'w') as sheet_file:
50
+ self._write_worksheet_bin(sheet_file, sheet_data_iterable, i)
51
+
52
+ self._zf.writestr(f"xl/worksheets/_rels/sheet{sheet_id}.bin.rels", self._create_worksheet_rels(sheet_id))
53
+ self._zf.writestr(f"xl/worksheets/binaryIndex{sheet_id}.bin", self._binaryIndexBin)
54
+
55
+ _zf: zipfile.ZipFile|None = None
56
+
57
+ def save(self):
58
+ self._zf.writestr("[Content_Types].xml", self._create_content_types())
59
+ self._zf.writestr("_rels/.rels", self._create_root_rels())
60
+ self._zf.writestr("xl/workbook.bin", self._create_workbook_bin())
61
+ self._zf.writestr("xl/styles.bin", self._stylesBin)
62
+ self._zf.writestr("xl/_rels/workbook.bin.rels", self._create_workbook_rels())
63
+
64
+ # After all worksheets are processed, the shared strings table is complete.
65
+ # Now, write it to the zip file in a streaming fashion.
66
+ with self._zf.open("xl/sharedStrings.bin", 'w') as sst_file:
67
+ self._write_shared_strings_bin(sst_file)
68
+
69
+ self._zf.close()
70
+
71
+
72
+ def _create_content_types(self) -> str:
73
+ parts = "".join(
74
+ f'<Override PartName="/xl/worksheets/sheet{i + 1}.bin" ContentType="application/vnd.ms-excel.worksheet"/>'
75
+ f'<Override PartName="/xl/worksheets/binaryIndex{i + 1}.bin" ContentType="application/vnd.ms-excel.binIndexWs"/>'
76
+ for i in range(self._sheet_count)
77
+ )
78
+
79
+ return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
80
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
81
+ <Default Extension="bin" ContentType="application/vnd.ms-excel.sheet.binary.macroEnabled.main"/>
82
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
83
+ <Default Extension="xml" ContentType="application/xml"/>
84
+ <Override PartName="/xl/workbook.bin" ContentType="application/vnd.ms-excel.sheet.binary.macroEnabled.main"/>
85
+ {parts}
86
+ <Override PartName="/xl/styles.bin" ContentType="application/vnd.ms-excel.styles"/>
87
+ <Override PartName="/xl/sharedStrings.bin" ContentType="application/vnd.ms-excel.sharedStrings"/>
88
+ </Types>'''
89
+
90
+ def _create_root_rels(self) -> str:
91
+ return '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
92
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
93
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.bin"/>
94
+ </Relationships>'''
95
+
96
+ def _create_workbook_rels(self) -> str:
97
+ relationships = [
98
+ f'<Relationship Id="rId{i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet{i + 1}.bin"/>'
99
+ for i in range(self._sheet_count)
100
+ ]
101
+
102
+ style_rid = self._sheet_count + 1
103
+ shared_strings_rid = self._sheet_count + 2
104
+ relationships.append(f'<Relationship Id="rId{style_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.bin"/>')
105
+ relationships.append(f'<Relationship Id="rId{shared_strings_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.bin"/>')
106
+
107
+ return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
108
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
109
+ {"".join(relationships)}
110
+ </Relationships>'''
111
+
112
+ def _create_worksheet_rels(self, sheet_id) -> str:
113
+ return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
114
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
115
+ <Relationship Id="rId1" Type="http://schemas.microsoft.com/office/2006/relationships/xlBinaryIndex" Target="binaryIndex{sheet_id}.bin"/>
116
+ </Relationships>'''
117
+
118
+ def _create_workbook_bin(self) -> bytes:
119
+ data = bytearray(self._workbookBinStart)
120
+ for i, (sheet_name, _, hidden) in enumerate(self._worksheet_data):
121
+ sheet_id = i + 1
122
+ r_id = f"rId{sheet_id}"
123
+
124
+ record_length = 4 + 3 * 4 + len(sheet_name) * 2 + len(r_id) * 2
125
+
126
+ data.extend(b'\x9C\x01')
127
+ data.extend(self._write_custom_variable_int(record_length))
128
+
129
+ if hidden:
130
+ data.extend(struct.pack('<I', 1))
131
+ else:
132
+ data.extend(struct.pack('<I', 0))
133
+
134
+ data.extend(struct.pack('<I', sheet_id))
135
+ data.extend(struct.pack('<I', len(r_id)))
136
+ data.extend(r_id.encode('utf-16-le'))
137
+ data.extend(struct.pack('<I', len(sheet_name)))
138
+ data.extend(sheet_name.encode('utf-16-le'))
139
+
140
+ data.extend(self._workbookBinMiddle)
141
+ self._write_filter_defined_names(data);
142
+ data.extend(self._workbookBinEnd)
143
+ return bytes(data)
144
+
145
+ def _write_custom_variable_int(self, value: int) -> bytes:
146
+ if value < 0:
147
+ raise ValueError("Value must be non-negative")
148
+
149
+ buffer = bytearray()
150
+ temp_val = value
151
+ while True:
152
+ byte = temp_val & 0x7F
153
+ temp_val >>= 7
154
+ if temp_val > 0:
155
+ byte |= 0x80
156
+ buffer.append(byte)
157
+ else:
158
+ buffer.append(byte)
159
+ break
160
+ return bytes(buffer)
161
+
162
+ def _write_shared_strings_bin(self, sst_file: io.BufferedWriter):
163
+ buffer = io.BytesIO()
164
+ BUFFER_SIZE = 65536
165
+
166
+ buffer.write(b'\x9F\x01\x08')
167
+ buffer.write(struct.pack('<II', self._sst_all_count, self._sst_unique_count))
168
+
169
+ for s in self._shared_strings:
170
+ str_len = len(s)
171
+ if str_len > 32767:
172
+ str_len = 32767
173
+ s = s[:str_len]
174
+
175
+ record_length = 1 + 4 + 2 * str_len
176
+
177
+ buffer.write(b'\x13')
178
+ buffer.write(self._write_custom_variable_int(record_length))
179
+ buffer.write(b'\x00')
180
+ buffer.write(struct.pack('<I', str_len))
181
+ buffer.write(s.encode('utf-16-le'))
182
+
183
+ if buffer.tell() > BUFFER_SIZE:
184
+ sst_file.write(buffer.getvalue())
185
+ buffer.seek(0)
186
+ buffer.truncate()
187
+
188
+ buffer.write(b'\xA0\x01\x00')
189
+
190
+ if buffer.tell() > 0:
191
+ sst_file.write(buffer.getvalue())
192
+
193
+ def _write_cols_width(self, buffer: io.BytesIO, start_col: int, end_col: int, col_widths_array: list[float]):
194
+ """
195
+ Write column width information to the buffer.
196
+ Based on the C# WriteColsWidth method from XlsbWriter.cs
197
+ """
198
+ # widths !!!
199
+ buffer.write(b'\x86\x03') # 134, 3
200
+
201
+ l = 0
202
+ for i in range(start_col, end_col):
203
+ # start of column definition
204
+ buffer.write(b'\x00\x3C\x12') # 0, 60, 18
205
+
206
+ # column min
207
+ buffer.write(struct.pack('<I', i))
208
+ # column max
209
+ buffer.write(struct.pack('<I', i))
210
+
211
+ # width
212
+ buffer.write(b'\x00')
213
+ buffer.write(struct.pack('<B', int(col_widths_array[l]))) # .. x 7 = pixels
214
+ buffer.write(b'\x00\x00')
215
+
216
+ buffer.write(b'\x00\x00\x00\x00')
217
+ buffer.write(b'\x02') # column properties /hidden etc, 2 = normal
218
+ # end of column definition
219
+ l += 1
220
+
221
+ buffer.write(b'\x00\x87\x03\x00') # 0, 135, 3, 0
222
+
223
+ _sheetCnt = 1
224
+ def _write_worksheet_bin(self, sheet_file: io.BufferedWriter, worksheet_data: Iterable[list[any]], worksheet_index:int):
225
+ buffer = io.BytesIO()
226
+ BUFFER_SIZE = 65536
227
+
228
+ buffer.write(self._sheet1Bytes[:54])
229
+ if self._sheetCnt != 1:
230
+ buffer.write(b'\x9C')
231
+ else:
232
+ buffer.write(b'\xDC')
233
+ self._sheetCnt +=1
234
+
235
+ buffer.write(self._sheet1Bytes[55:84])
236
+
237
+ buffer.write(self._stickHeaderA1bytes)
238
+
239
+ # Calculate column information by reading first 100 rows for better width estimation
240
+ data_iterator = iter(worksheet_data)
241
+ rows_to_analyze = []
242
+ max_cols = 1
243
+
244
+ # Read up to 100 rows for analysis
245
+ for _ in range(100):
246
+ try:
247
+ row = next(data_iterator)
248
+ rows_to_analyze.append(row)
249
+ max_cols = max(max_cols, len(row) if row else 0)
250
+ except StopIteration:
251
+ break # No more rows available
252
+
253
+ if max_cols == 0:
254
+ max_cols = 1
255
+
256
+ start_col = 0
257
+ end_col = max_cols
258
+
259
+ # Create column widths based on analyzed rows content
260
+ col_widths_array = []
261
+ for i in range(max_cols):
262
+ max_width = 0
263
+ for row in rows_to_analyze:
264
+ cell = row[i]
265
+ if i < len(row) and cell is not None:
266
+ if cell is Decimal:
267
+ cell = float(cell)
268
+ lenn = len(str(cell))
269
+ temp_width = 1.25 * lenn + 4
270
+ max_width = max(max_width, temp_width)
271
+
272
+ if max_width == 0:
273
+ max_width = 1.25 * 0 + 4 # Empty column
274
+ col_widths_array.append(max_width)
275
+
276
+ # Create iterator that starts with analyzed rows, then continues with the rest
277
+ if rows_to_analyze:
278
+ data_iterator = itertools.chain(rows_to_analyze, data_iterator)
279
+ else:
280
+ data_iterator = iter([])
281
+
282
+ buffer.write(self._sheet1Bytes[84:159])
283
+
284
+ # Write column widths
285
+ self._write_cols_width(buffer, start_col, end_col, col_widths_array)
286
+
287
+ buffer.write(self._sheet1Bytes[159:175])
288
+ buffer.write(b'\x26\x00')
289
+ buffer.write(b'\x91\x01\x00')
290
+
291
+ shared_strings_dict = self._shared_strings_dict
292
+ shared_strings_append = self._shared_strings.append
293
+
294
+ for row_idx, row in enumerate(data_iterator):
295
+ last_col = len(row) - 1 if row else 0
296
+
297
+ buffer.write(b'\x00\x19')
298
+ buffer.write(struct.pack('<I', row_idx))
299
+ buffer.write(b'\x00\x00\x00\x00')
300
+ buffer.write(struct.pack('<I', 0x12c))
301
+ buffer.write(b'\x00\x01\x00\x00\x00')
302
+ buffer.write(struct.pack('<II', 0, last_col))
303
+
304
+ for col_idx, cell in enumerate(row):
305
+ if cell is None:
306
+ continue
307
+ if isinstance(cell, str):
308
+ self._sst_all_count += 1
309
+ if cell not in shared_strings_dict:
310
+ string_index = self._sst_unique_count
311
+ shared_strings_dict[cell] = string_index
312
+ shared_strings_append(cell)
313
+ self._sst_unique_count += 1
314
+ else:
315
+ string_index = shared_strings_dict[cell]
316
+
317
+ style_ref = 3 if row_idx == 0 else 0
318
+ buffer.write(b'\x07\x0C')
319
+ buffer.write(struct.pack('<III', col_idx, style_ref, string_index))
320
+ elif isinstance(cell, bool):
321
+ buffer.write(b'\x04\x09')
322
+ buffer.write(struct.pack('<IIB', col_idx, 0, 1 if cell else 0))
323
+ elif isinstance(cell, int):
324
+ if -536870912 <= cell <= 536870911:
325
+ rk_val = (cell << 2) | 0b10
326
+ buffer.write(b'\x02\x0C')
327
+ buffer.write(struct.pack('<IIi', col_idx, 0, rk_val))
328
+ else:
329
+ buffer.write(b'\x05\x10')
330
+ buffer.write(struct.pack('<IId', col_idx, 0, float(cell)))
331
+ elif isinstance(cell, float):
332
+ buffer.write(b'\x05\x10')
333
+ buffer.write(struct.pack('<IId', col_idx, 0, cell))
334
+ elif isinstance(cell, Decimal):
335
+ buffer.write(b'\x05\x10')
336
+ buffer.write(struct.pack('<IId', col_idx, 0, float(cell)))
337
+ elif isinstance(cell, datetime.datetime):
338
+ excel_date = (cell - datetime.datetime(1899, 12, 30)).total_seconds() / 86400.0
339
+ buffer.write(b'\x05\x10')
340
+ buffer.write(struct.pack('<IId', col_idx, 1, excel_date))
341
+ elif isinstance(cell, datetime.date):
342
+ excel_date = (datetime.datetime.combine(cell, datetime.time()) - datetime.datetime(1899, 12, 30)).total_seconds() / 86400.0
343
+ buffer.write(b'\x05\x10')
344
+ buffer.write(struct.pack('<IId', col_idx, 2, excel_date))
345
+ else:
346
+ cell_str = str(cell)
347
+ self._sst_all_count += 1
348
+ if cell_str not in shared_strings_dict:
349
+ string_index = self._sst_unique_count
350
+ shared_strings_dict[cell_str] = string_index
351
+ shared_strings_append(cell_str)
352
+ self._sst_unique_count += 1
353
+ else:
354
+ string_index = shared_strings_dict[cell_str]
355
+ buffer.write(b'\x07\x0C')
356
+ buffer.write(struct.pack('<III', col_idx, 0, string_index))
357
+
358
+ if buffer.tell() > BUFFER_SIZE:
359
+ sheet_file.write(buffer.getvalue())
360
+ buffer.seek(0)
361
+ buffer.truncate()
362
+
363
+ buffer.write(self._sheet1Bytes[218:290])
364
+
365
+
366
+ # doAutofilter
367
+ buffer.write(self._autoFilterStartBytes)
368
+ buffer.write(struct.pack('<I', 0))
369
+ buffer.write(struct.pack('<I', row_idx))
370
+ buffer.write(struct.pack('<I', 0))
371
+ buffer.write(struct.pack('<I', 0 + end_col - 1))
372
+ buffer.write(self._autoFilterEndBytes)
373
+ self._filtered_data_list.append(
374
+ FilterData(
375
+ sheet_index=worksheet_index,
376
+ start_column=0,
377
+ end_column=end_col - 1,
378
+ start_row=0,
379
+ end_row=row_idx
380
+ ))
381
+
382
+ buffer.write(self._sheet1Bytes[290:])
383
+ if buffer.tell() > 0:
384
+ sheet_file.write(buffer.getvalue())
385
+
386
+ def _write_filter_defined_names(self, sw: bytearray) -> None:
387
+ filtered_dict_items_cnt = len(self._filtered_data_list) if self._filtered_data_list else 0
388
+
389
+ # Temporary fix for https://github.com/KrzysztofDusko/SpreadSheetTasks/issues/2
390
+ if (filtered_dict_items_cnt >= 0 and
391
+ (0x80 + (filtered_dict_items_cnt - 21) * 0x0c) <= 255):
392
+
393
+ sw.extend(self._magicFilterExcel2016Fix0)
394
+
395
+ if filtered_dict_items_cnt <= 10:
396
+ # !!! ? for cnt <=10
397
+ sw.extend(bytes([0x10 + (filtered_dict_items_cnt - 1) * 0x0c,
398
+ filtered_dict_items_cnt]))
399
+ sw.extend(bytes([0x00, 0x00, 0x00]))
400
+
401
+ elif filtered_dict_items_cnt <= 20:
402
+ sw.extend(bytes([0x10 + (filtered_dict_items_cnt - 1) * 0x0c,
403
+ (filtered_dict_items_cnt - 1) // 10,
404
+ filtered_dict_items_cnt]))
405
+ sw.extend(bytes([0x00, 0x00, 0x00]))
406
+
407
+ else: # ???
408
+ sw.extend(bytes([0x80 + (filtered_dict_items_cnt - 21) * 0x0c,
409
+ (filtered_dict_items_cnt - 1) // 10,
410
+ filtered_dict_items_cnt]))
411
+ sw.extend(bytes([0x00, 0x00, 0x00]))
412
+
413
+ # Write filter items
414
+ for nm in range(filtered_dict_items_cnt):
415
+ sw.extend(bytes([0x00, 0x00, 0x00, 0x00]))
416
+ sw.extend(bytes([self._filtered_data_list[nm].sheet_index, 0x00, 0x00, 0x00]))
417
+ sw.extend(bytes([self._filtered_data_list[nm].sheet_index, 0x00, 0x00, 0x00]))
418
+
419
+ sw.extend(bytes([0xE2, 0x02, 0x00]))
420
+
421
+ # Write sheet data
422
+ for sheet_num in range(len(self._filtered_data_list)):
423
+ start_column = self._filtered_data_list[sheet_num].start_column
424
+ end_column = self._filtered_data_list[sheet_num].end_column
425
+ start_row = self._filtered_data_list[sheet_num].start_row
426
+ end_row = self._filtered_data_list[sheet_num].end_row
427
+ sheet_index = self._filtered_data_list[sheet_num].sheet_index
428
+
429
+ # Modify magic bytes
430
+ self._magicFilterExcel2016Fix1[7] = sheet_index
431
+ self._magicFilterExcel2016Fix1[-2] = sheet_num
432
+ sw.extend(self._magicFilterExcel2016Fix1)
433
+
434
+
435
+ # Write binary data using struct.pack for proper byte conversion
436
+ sw.extend(struct.pack('<I', start_row)) # Int32 little-endian
437
+ sw.extend(struct.pack('<I', end_row)) # Int32 little-endian
438
+ sw.extend(struct.pack('<H', start_column)) # Int16 little-endian
439
+ sw.extend(struct.pack('<H', end_column)) # Int16 little-endian
440
+
441
+ sw.extend(self._magicFilterExcel2016Fix2)
442
+
443
+
444
+ _autoFilterStartBytes = bytes([0xA1, 0x01, 0x10])
445
+ _sheet1Bytes = bytes([
446
+ 0x81,0x01,0x00,0x93,0x01,0x17,0xCB,0x04,0x02,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,
447
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x94,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
448
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x85,0x01,0x00,0x89,0x01,0x1E,0xDC,0x03,0x00,0x00,0x00,0x00,
449
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
450
+ 0x00,0x00,0x00,0x00,0x98,0x01,0x24,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
451
+ 0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
452
+ 0x00,0x00,0x00,0x8A,0x01,0x00,0x86,0x01,0x00,0x25,0x06,0x01,0x00,0x02,0x0E,0x00,0x80,0x95,0x08,0x02,
453
+ 0x05,0x00,0x26,0x00,0xE5,0x03,0x0C,0xFF,0xFF,0xFF,0xFF,0x08,0x00,0x2C,0x01,0x00,0x00,0x00,0x00,0x91,
454
+ 0x01,0x00,0x25,0x06,0x01,0x00,0x02,0x0E,0x00,0x80,0x80,0x08,0x02,0x05,0x00,0x26,0x00,0x00,0x19,0x00,
455
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2C,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
456
+ 0x00,0x00,0x00,0x00,0x07,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0x01,
457
+ 0x00,0x97,0x04,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
458
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
459
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
460
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xDD,0x03,0x02,0x10,0x00,0xDC,0x03,0x30,0x66,0x66,
461
+ 0x66,0x66,0x66,0x66,0xE6,0x3F,0x66,0x66,0x66,0x66,0x66,0x66,0xE6,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,
462
+ 0xE8,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0xE8,0x3F,0x33,0x33,0x33,0x33,0x33,0x33,0xD3,0x3F,0x33,0x33,
463
+ 0x33,0x33,0x33,0x33,0xD3,0x3F,0x25,0x06,0x01,0x00,0x00,0x10,0x00,0x80,0x80,0x18,0x10,0x00,0x00,0x00,
464
+ 0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x82,0x01,0x00
465
+ ])
466
+ _stylesBin = bytes([
467
+ 0x96,0x02,0x00,0xE7,0x04,0x04,0x02,
468
+ 0x00,0x00,0x00,0x2C,0x2C,0xA4,0x00,0x13,
469
+ 0x00,0x00,0x00,0x79,0x00,0x79,0x00,0x79,
470
+ 0x00,0x79,0x00,0x5C,0x00,0x2D,0x00,0x6D,
471
+ 0x00,0x6D,0x00,0x5C,0x00,0x2D,0x00,0x64,
472
+ 0x00,0x64,0x00,0x5C,0x00,0x20,0x00,0x68,
473
+ 0x00,0x68,0x00,0x3A,0x00,0x6D,0x00,0x6D,
474
+ 0x00,0x2C,0x1E,0xA6,0x00,0x0C,0x00,0x00,
475
+ 0x00,0x79,0x00,0x79,0x00,0x79,0x00,0x79,
476
+ 0x00,0x5C,0x00,0x2D,0x00,0x6D,0x00,0x6D,
477
+ 0x00,0x5C,0x00,0x2D,0x00,0x64,0x00,0x64,
478
+ 0x00,0xE8,0x04,0x00,0xE3,0x04,0x04,0x01,
479
+ 0x00,0x00,0x00,
480
+ 0x2B,0x27,0xDC,0x00,0x00,0x00,0x90,0x01,
481
+ 0x00,0x00,0x00,0x02,0x00,0x00,0x07,0x01,
482
+ 0x00,0x00,0x00,0x00,0x00,0xFF,0x02,0x07,
483
+ 0x00,0x00,0x00,0x43,0x00,0x61,0x00,0x6C,
484
+ 0x00,0x69,0x00,0x62,0x00,0x72,0x00,0x69,
485
+ 0x00,
486
+ 0x2B,0x27,0xDC,0x00,0x01,0x00,0xBC,0x02,
487
+ 0x00,0x00,0x00,0x02,0xEE,0x00,0x07,0x01,
488
+ 0x00,0x00,0x00,0x00,0x00,0xFF,0x02,0x07,
489
+ 0x00,0x00,0x00,0x43,0x00,0x61,0x00,0x6C,
490
+ 0x00,0x69,0x00,0x62,0x00,0x72,0x00,0x69,
491
+ 0x00,
492
+ 0x25,0x06,0x01,0x00,0x02,0x0E,0x00,0x80,0x81,0x08,0x00,0x26,0x00,0xE4,0x04,0x00,0xDB,0x04,0x04,0x02,0x00,0x00,0x00,
493
+ 0x2D,0x44,0x00,0x00,0x00,0x00,0x03,0x40,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0x41,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,
494
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
495
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
496
+ 0x00,0x2D,0x44,0x11,0x00,0x00,0x00,0x03,0x40,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0x41,0x00,0x00,0xFF,0xFF,0xFF,0xFF,
497
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
498
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
499
+ 0x00,0x00,0xDC,0x04,0x00,0xE5,0x04,0x04,0x01,0x00,0x00,0x00,0x2E,0x33,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
500
+ 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
501
+ 0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE6,0x04,0x00,0xF2,
502
+ 0x04,0x04,0x01,0x00,0x00,0x00,0x2F,0x10,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x00,
503
+ 0x00,0xF3,0x04,0x00,0xE9,0x04,0x04,
504
+ 0x04,
505
+ 0x00,0x00,0x00,
506
+ 0x2F,0x10,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x00,0x00,
507
+ 0x2F,0x10,0x00,0x00,0xA4,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x01,0x00,
508
+ 0x2F,0x10,0x00,0x00,0xA6,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x01,0x00,
509
+ 0x2F,0x10,0x00,0x00,0x00,0x01, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x00,0x00,
510
+ 0xEA,0x04,0x00,0xEB,0x04,0x04,0x01,0x00,
511
+ 0x00,0x00,0x25,0x06,0x01,0x00,0x02,0x11,0x00,0x80,0x80,0x18,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
512
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x30,0x1C,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x4E,
513
+ 0x00,0x6F,0x00,0x72,0x00,0x6D,0x00,0x61,0x00,0x6C,0x00,0x6E,0x00,0x79,0x00,0xEC,0x04,0x00,0xF9,0x03,0x04,0x00,0x00,
514
+ 0x00,0x00,0xFA,0x03,0x00,0xFC,0x03,0x50,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x54,0x00,0x61,0x00,0x62,0x00,0x6C,
515
+ 0x00,0x65,0x00,0x53,0x00,0x74,0x00,0x79,0x00,0x6C,0x00,0x65,0x00,0x4D,0x00,0x65,0x00,0x64,0x00,0x69,0x00,0x75,0x00,
516
+ 0x6D,0x00,0x32,0x00,0x11,0x00,0x00,0x00,0x50,0x00,0x69,0x00,0x76,0x00,0x6F,0x00,0x74,0x00,0x53,0x00,0x74,0x00,0x79,
517
+ 0x00,0x6C,0x00,0x65,0x00,0x4C,0x00,0x69,0x00,0x67,0x00,0x68,0x00,0x74,0x00,0x31,0x00,0x36,0x00,0xFD,0x03,0x00,0x23,
518
+ 0x04,0x02,0x0E,0x00,0x00,0xEB,0x08,0x00,0xF6,0x08,0x2A,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x53,0x00,0x6C,0x00,
519
+ 0x69,0x00,0x63,0x00,0x65,0x00,0x72,0x00,0x53,0x00,0x74,0x00,0x79,0x00,0x6C,0x00,0x65,0x00,0x4C,0x00,0x69,0x00,0x67,
520
+ 0x00,0x68,0x00,0x74,0x00,0x31,0x00,0xF7,0x08,0x00,0xEC,0x08,0x00,0x24,0x00,0x23,0x04,0x03,0x0F,0x00,0x00,0xB0,0x10,
521
+ 0x00,0xB2,0x10,0x32,0x00,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x54,0x00,0x69,0x00,0x6D,0x00,0x65,0x00,0x53,0x00,0x6C,
522
+ 0x00,0x69,0x00,0x63,0x00,0x65,0x00,0x72,0x00,0x53,0x00,0x74,0x00,0x79,0x00,0x6C,0x00,0x65,0x00,0x4C,0x00,0x69,0x00,
523
+ 0x67,0x00,0x68,0x00,0x74,0x00,0x31,0x00,0xB3,0x10,0x00,0xB1,0x10,0x00,0x24,0x00,0x97,0x02,0x00
524
+ ])
525
+ _workbookBinStart = bytes([
526
+ 0x83,0x01,0x00,0x80,0x01,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
527
+ 0x00,0x00,0x02,0x00,0x00,0x00,0x78,0x00,0x6C,0x00,0x01,0x00,0x00,0x00,0x37,0x00,0x01,0x00,0x00,0x00,
528
+ 0x36,0x00,0x05,0x00,0x00,0x00,0x32,0x00,0x34,0x00,0x33,0x00,0x32,0x00,0x36,0x00,0x99,0x01,0x0C,0x20,
529
+ 0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x25,0x06,0x01,0x00,0x03,0x0F,0x00,0x80,0x97,
530
+ 0x10,0x34,0x18,0x00,0x00,0x00,0x43,0x00,0x3A,0x00,0x5C,0x00,0x73,0x00,0x71,0x00,0x6C,0x00,0x73,0x00,
531
+ 0x5C,0x00,0x54,0x00,0x65,0x00,0x73,0x00,0x74,0x00,0x79,0x00,0x5A,0x00,0x61,0x00,0x70,0x00,0x69,0x00,
532
+ 0x73,0x00,0x75,0x00,0x58,0x00,0x6C,0x00,0x73,0x00,0x62,0x00,0x5C,0x00,0x26,0x00,0x25,0x06,0x01,0x00,
533
+ 0x00,0x10,0x00,0x80,0x81,0x18,0x82,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2F,0x00,0x00,0x00,
534
+ 0x31,0x00,0x33,0x00,0x5F,0x00,0x6E,0x00,0x63,0x00,0x72,0x00,0x3A,0x00,0x31,0x00,0x5F,0x00,0x7B,0x00,
535
+ 0x31,0x00,0x36,0x00,0x35,0x00,0x30,0x00,0x38,0x00,0x44,0x00,0x36,0x00,0x39,0x00,0x2D,0x00,0x43,0x00,
536
+ 0x46,0x00,0x38,0x00,0x37,0x00,0x2D,0x00,0x34,0x00,0x37,0x00,0x36,0x00,0x39,0x00,0x2D,0x00,0x38,0x00,
537
+ 0x34,0x00,0x35,0x00,0x36,0x00,0x2D,0x00,0x44,0x00,0x34,0x00,0x41,0x00,0x34,0x00,0x30,0x00,0x31,0x00,
538
+ 0x31,0x00,0x33,0x00,0x31,0x00,0x35,0x00,0x36,0x00,0x37,0x00,0x7D,0x00,0x2F,0x00,0x00,0x00,0x2F,0x00,
539
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,
540
+ 0x87,0x01,0x00,0x25,0x06,0x01,0x00,0x02,0x10,0x00,0x80,0x80,0x18,0x10,0x00,0x00,0x00,0x00,0x0D,0x00,
541
+ 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x26,0x00,0x9E,0x01,0x1D,0x00,0x00,0x00,0x00,0x9E,
542
+ 0x16,0x00,0x00,0xB4,0x69,0x00,0x00,0xE8,0x26,0x00,0x00,0x58,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
543
+ 0x00,0x00,0x00,0x78,0x88,0x01,0x00,0x8F,0x01,0x00
544
+ ])
545
+ _workbookBinMiddle = bytes([0x90,0x01,0x00])
546
+ _workbookBinEnd = bytes([
547
+ 0x9D,0x01,0x1A,0x35,0xEA,0x02,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0xFC,0xA9,0xF1,0xD2,0x4D,
548
+ 0x62,0x50,0x3F,0x01,0x00,0x00,0x00,0x6A,0x00,0x9B,0x01,0x01,0x00,0x23,0x04,0x03,0x0F,0x00,0x00,0xAB,
549
+ 0x10,0x01,0x01,0x24,0x00,0x84,0x01,0x00
550
+ ])
551
+ _binaryIndexBin = bytes([
552
+ 0x2A,0x18,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
553
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x95,0x02,0x00
554
+ ])
555
+ _stickHeaderA1bytes =bytes([
556
+ 0x97,0x01,0x1D,0x00,0x00,0x00,0x00,0x00,
557
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
558
+ 0x00,0xF0,0x3F,0x01,0x00,0x00,0x00,0x00,
559
+ 0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,
560
+ ])
561
+ _autoFilterEndBytes = bytes([0xA2, 0x01, 0x00])
562
+ _magicFilterExcel2016Fix0 = bytes([0xE1, 0x02, 0x00, 0xE5, 0x02, 0x00, 0xEA, 0x02])
563
+ _magicFilterExcel2016Fix1 = bytearray([
564
+ 0x27,
565
+ 0x46,
566
+ 0x21,
567
+ 0x00,
568
+ 0x00,
569
+ 0x00,
570
+ 0x00,
571
+ 255,# -> (byte)sheetIndex,
572
+ 0x00,
573
+ 0x00,
574
+ 0x00,
575
+ 0x0F,
576
+ 0x00,
577
+ 0x00,
578
+ 0x00,
579
+ 0x5F,
580
+ 0x00, # _0, // FilterDatabase (UTF16) - starts
581
+ 0x46,
582
+ 0x00,
583
+ 0x69,
584
+ 0x00,
585
+ 0x6C,
586
+ 0x00,
587
+ 0x74,
588
+ 0x00,
589
+ 0x65,
590
+ 0x00,
591
+ 0x72,
592
+ 0x00,
593
+ 0x44,
594
+ 0x00,
595
+ 0x61,
596
+ 0x00,
597
+ 0x74,
598
+ 0x00,
599
+ 0x61,
600
+ 0x00,
601
+ 0x62,
602
+ 0x00,
603
+ 0x61,
604
+ 0x00,
605
+ 0x73,
606
+ 0x00,
607
+ 0x65,
608
+ 0x00,# FilterDatabase (UTF16) - ends
609
+ 0x0F,
610
+ 0x00,
611
+ 0x00,
612
+ 0x00,
613
+ 0x3B,
614
+ 255,#->(byte)sheetNum,
615
+ 0x00
616
+ ])
617
+ _magicFilterExcel2016Fix2 = bytes([0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF])
618
+
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyxlsbwriter
3
+ Version: 0.0.1
4
+ Summary: A Python library to write XLSB files.
5
+ Author: Krzysztof Duśko
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: xlsxwriter
12
+ Provides-Extra: test
13
+ Requires-Dist: pyodbc; extra == "test"
14
+ Provides-Extra: examples
15
+ Requires-Dist: memory-profiler; extra == "examples"
16
+
17
+ # Python XLSB Writer
18
+
19
+ A Python library for writing large data sets to XLSB files efficiently.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install pyxlsbwriter
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from pyxlsbwriter import XlsbWriter
31
+ import datetime
32
+ from decimal import Decimal
33
+
34
+ data = [
35
+ ["Name", "Age", "City", "info"],
36
+ [-123, 2147483647, 2147483648, 2147483999],
37
+ ["x", "y", "z", datetime.datetime.today()],
38
+ ["Alice", 25, "New York", datetime.date.today()],
39
+ ["Bob", 30, "London", Decimal(3.14)],
40
+ ["Charlie", 35, "Paris", datetime.datetime.now()],
41
+ [True, False, None, datetime.datetime.utcnow()]
42
+ ]
43
+
44
+ writer = XlsbWriter("test.xlsb")
45
+ writer.add_sheet("Sheet1")
46
+ writer.write_sheet(data)
47
+ writer.save()
48
+ ```
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/pyxlsbwriter/__init__.py
4
+ src/pyxlsbwriter/writer.py
5
+ src/pyxlsbwriter.egg-info/PKG-INFO
6
+ src/pyxlsbwriter.egg-info/SOURCES.txt
7
+ src/pyxlsbwriter.egg-info/dependency_links.txt
8
+ src/pyxlsbwriter.egg-info/requires.txt
9
+ src/pyxlsbwriter.egg-info/top_level.txt
10
+ tests/test_real_data.py
@@ -0,0 +1,7 @@
1
+ xlsxwriter
2
+
3
+ [examples]
4
+ memory-profiler
5
+
6
+ [test]
7
+ pyodbc
@@ -0,0 +1 @@
1
+ pyxlsbwriter
@@ -0,0 +1,102 @@
1
+ import os
2
+ from typing import Generator
3
+ import pyodbc
4
+ from pyxlsbwriter import XlsbWriter
5
+
6
+ # --- CONFIGURATION ---
7
+ # CHANGE THE VALUES BELOW TO MATCH YOUR NETEZZA DATABASE
8
+ # Make sure you have an ODBC data source (DSN) configured for Netezza on your system.
9
+ password = os.environ.get("NZ_DEV_PASSWORD", "password")
10
+ DSN = f"DRIVER={{NetezzaSQL}};SERVER=linux.local;PORT=5480;DATABASE=JUST_DATA;UID=admin;PWD={password};"
11
+ # Sample query. Change it to one that works on your database.
12
+ QUERY1 = "SELECT * FROM JUST_DATA..DIMACCOUNT"
13
+ QUERY2 = "SELECT * FROM JUST_DATA..DIMCURRENCY"
14
+ OUTPUT_FILENAME = "real_netezza_output.xlsb"
15
+
16
+
17
+ def row_generator(cursor: pyodbc.Cursor) -> Generator[list[any], None, None]:
18
+ """
19
+ Generates rows from a pyodbc cursor, yielding headers first, followed by data rows.
20
+
21
+ Args:
22
+ cursor: A pyodbc cursor object containing query results.
23
+
24
+ Yields:
25
+ List[Any]: First yields the list of column headers, then each row as a list.
26
+
27
+ Raises:
28
+ pyodbc.Error: If there's an error accessing the cursor's data.
29
+ """
30
+ try:
31
+ # Extract column headers from cursor description
32
+ headers = [column[0] for column in cursor.description]
33
+ yield headers
34
+
35
+ # Yield each row until the cursor is exhausted
36
+ while row := cursor.fetchone():
37
+ yield list(row)
38
+ except pyodbc.Error as e:
39
+ raise pyodbc.Error(f"Error processing cursor data: {e}")
40
+
41
+ def main():
42
+ """
43
+ Tests fetching data from a real Netezza database and writing to an XLSB file.
44
+ """
45
+ if os.path.exists(OUTPUT_FILENAME):
46
+ os.remove(OUTPUT_FILENAME)
47
+
48
+ try:
49
+ print(f"Connecting to the database using DSN: '{DSN.split(';')[0]}...'")
50
+ with pyodbc.connect(DSN) as conn:
51
+ cursor = conn.cursor()
52
+ print(f"Executing query: '{QUERY1}'")
53
+ cursor.execute(QUERY1)
54
+
55
+ # Use XlsbWriter to write the data
56
+ print(f"Writing data to file '{OUTPUT_FILENAME}'...")
57
+ writer = XlsbWriter(OUTPUT_FILENAME)
58
+ writer.add_sheet("Netezza Export")
59
+ writer.write_sheet(row_generator(cursor))
60
+
61
+ writer.add_sheet("query 2")
62
+ print(f"Executing query: '{QUERY2}'")
63
+ cursor.execute(QUERY2)
64
+ writer.write_sheet(row_generator(cursor))
65
+ writer.add_sheet("SQL1")
66
+ writer.write_sheet([["code"],[QUERY1]] ,hidden=True)
67
+ writer.add_sheet("SQL2")
68
+ writer.write_sheet([["code"],[QUERY2]] ,hidden=True)
69
+ writer.save()
70
+
71
+ except pyodbc.Error as ex:
72
+ sqlstate = ex.args[0]
73
+ print(f"Database connection or query execution error: {sqlstate}\n{ex}")
74
+ except Exception as e:
75
+ print(f"An unexpected error occurred: {e}")
76
+
77
+ # Check if the XLSB file was created and is not empty
78
+ if os.path.exists(OUTPUT_FILENAME) and os.path.getsize(OUTPUT_FILENAME) > 0:
79
+ print(f"\nTest completed successfully, file '{OUTPUT_FILENAME}' was created and contains data.")
80
+ else:
81
+ print(f"\nTest failed, file '{OUTPUT_FILENAME}' was not created or is empty.")
82
+
83
+ if os.path.exists(OUTPUT_FILENAME):
84
+ os.remove(OUTPUT_FILENAME)
85
+
86
+
87
+ if __name__ == '__main__':
88
+ # Checking if pyodbc is installed
89
+ try:
90
+ import pyodbc
91
+ main()
92
+ except ImportError:
93
+ print("pyodbc library is not installed. Installing...")
94
+ import subprocess
95
+ import sys
96
+ try:
97
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "pyodbc"])
98
+ main()
99
+ except subprocess.CalledProcessError as e:
100
+ print(f"Failed to install pyodbc. Please install it manually: pip install pyodbc")
101
+ sys.exit(1)
102
+