xlsxlite 1.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.

Potentially problematic release.


This version of xlsxlite might be problematic. Click here for more details.

xlsxlite/__init__.py ADDED
File without changes
File without changes
xlsxlite/test/base.py ADDED
@@ -0,0 +1,58 @@
1
+ import os
2
+ import pytest
3
+ import shutil
4
+ import unittest
5
+ from datetime import datetime, timedelta
6
+
7
+
8
+ @pytest.fixture
9
+ def tests_dir():
10
+ os.mkdir("_tests")
11
+ yield
12
+ shutil.rmtree("_tests")
13
+
14
+
15
+ class XLSXTest(unittest.TestCase):
16
+ def assertExcelRow(self, sheet, row_num, values, tz=None):
17
+ """
18
+ Asserts the cell values in the given worksheet row. Date values are converted using the provided timezone.
19
+ """
20
+ expected_values = []
21
+ for expected in values:
22
+ # if expected value is datetime, localize and remove microseconds
23
+ if isinstance(expected, datetime):
24
+ expected = expected.astimezone(tz).replace(microsecond=0, tzinfo=None)
25
+
26
+ expected_values.append(expected)
27
+
28
+ rows = tuple(sheet.rows)
29
+
30
+ actual_values = []
31
+ for cell in rows[row_num]:
32
+ actual = cell.value
33
+
34
+ if actual is None:
35
+ actual = ""
36
+
37
+ if isinstance(actual, datetime):
38
+ actual = actual
39
+
40
+ actual_values.append(actual)
41
+
42
+ for index, expected in enumerate(expected_values):
43
+ actual = actual_values[index]
44
+
45
+ if isinstance(expected, datetime):
46
+ close_enough = abs(expected - actual) < timedelta(seconds=1)
47
+ assert close_enough, f"Datetime value {expected} doesn't match {actual}"
48
+ else:
49
+ assert expected == actual
50
+
51
+ def assertExcelSheet(self, sheet, rows, tz=None):
52
+ """
53
+ Asserts the row values in the given worksheet
54
+ """
55
+ assert len(list(sheet.rows)) == len(rows)
56
+
57
+ for r, row in enumerate(rows):
58
+ self.assertExcelRow(sheet, r, row, tz)
@@ -0,0 +1,76 @@
1
+ import pytest
2
+ import random
3
+ import string
4
+ import xlsxwriter
5
+
6
+ from openpyxl import Workbook
7
+ from openpyxl.cell.cell import WriteOnlyCell
8
+ from openpyxl.cell._writer import etree_write_cell
9
+ from unittest.mock import patch
10
+ from xlsxlite.writer import XLSXBook
11
+ from .base import tests_dir # noqa
12
+
13
+
14
+ NUM_ROWS = 1000
15
+ NUM_COLS = 10
16
+
17
+ # generate some random strings to use as cell values
18
+ DATA = ["".join(random.choices(string.ascii_uppercase + string.digits, k=16)) for d in range(1000)]
19
+
20
+
21
+ @pytest.mark.usefixtures("tests_dir")
22
+ def test_xlxslite():
23
+ book = XLSXBook()
24
+ sheet1 = book.add_sheet("Sheet1")
25
+
26
+ for r in range(NUM_ROWS):
27
+ row = [DATA[(r * c) % len(DATA)] for c in range(NUM_COLS)]
28
+
29
+ sheet1.append_row(*row)
30
+
31
+ book.finalize(to_file="_tests/test.xlsx")
32
+
33
+
34
+ @pytest.mark.usefixtures("tests_dir")
35
+ @patch("openpyxl.cell._writer.write_cell")
36
+ def test_openpyxl_etree(mock_write_cell):
37
+ mock_write_cell.side_effect = etree_write_cell
38
+
39
+ book = Workbook(write_only=True)
40
+ sheet1 = book.create_sheet("Sheet1")
41
+
42
+ for r in range(NUM_ROWS):
43
+ row = [DATA[(r * c) % len(DATA)] for c in range(NUM_COLS)]
44
+
45
+ cells = [WriteOnlyCell(sheet1, value=v) for v in row]
46
+ sheet1.append(cells)
47
+
48
+ book.save("_tests/test.xlsx")
49
+
50
+
51
+ @pytest.mark.usefixtures("tests_dir")
52
+ def test_openpyxl_lxml():
53
+ book = Workbook(write_only=True)
54
+ sheet1 = book.create_sheet("Sheet1")
55
+
56
+ for r in range(NUM_ROWS):
57
+ row = [DATA[(r * c) % len(DATA)] for c in range(NUM_COLS)]
58
+
59
+ cells = [WriteOnlyCell(sheet1, value=v) for v in row]
60
+ sheet1.append(cells)
61
+
62
+ book.save("_tests/test.xlsx")
63
+
64
+
65
+ @pytest.mark.usefixtures("tests_dir")
66
+ def test_xlsxwriter():
67
+ book = xlsxwriter.Workbook("_tests/test.xlsx")
68
+ sheet1 = book.add_worksheet()
69
+
70
+ for r in range(NUM_ROWS):
71
+ row = [DATA[(r * c) % len(DATA)] for c in range(NUM_COLS)]
72
+
73
+ for c, val in enumerate(row):
74
+ sheet1.write(r, c, val)
75
+
76
+ book.close()
@@ -0,0 +1,15 @@
1
+ from datetime import datetime
2
+
3
+ import pytest
4
+ import pytz
5
+
6
+ from xlsxlite.utils import datetime_to_serial
7
+
8
+
9
+ def test_datetime_to_serial():
10
+ assert datetime_to_serial(datetime(2013, 1, 1, 12, 0, 0)) == 41275.5
11
+ assert datetime_to_serial(datetime(2018, 6, 15, 11, 24, 30, 0)) == 43266.47534722222
12
+
13
+ # try with a non-naive datetime
14
+ with pytest.raises(ValueError):
15
+ datetime_to_serial(datetime(2018, 6, 15, 11, 24, 30, 0, pytz.UTC))
@@ -0,0 +1,88 @@
1
+ from datetime import datetime, timedelta
2
+
3
+ import pytest
4
+ from openpyxl.reader.excel import load_workbook
5
+ from unittest.mock import patch
6
+ from xlsxlite.writer import XLSXBook
7
+
8
+ from .base import XLSXTest, tests_dir # noqa
9
+
10
+
11
+ @pytest.mark.usefixtures("tests_dir")
12
+ class BookTest(XLSXTest):
13
+ def test_empty(self):
14
+ book = XLSXBook()
15
+ book.finalize(to_file="_tests/empty.xlsx")
16
+
17
+ book = load_workbook(filename="_tests/empty.xlsx")
18
+ assert len(book.worksheets) == 1
19
+ assert book.worksheets[0].title == "Sheet1"
20
+
21
+ def test_simple(self):
22
+ book = XLSXBook()
23
+ sheet1 = book.add_sheet("People")
24
+ sheet1.append_row("Name", "Email")
25
+ sheet1.append_row("Jim", "jim@acme.com")
26
+ sheet1.append_row("Bob", "bob@acme.com")
27
+
28
+ book.add_sheet("Empty")
29
+
30
+ # insert a new sheet at a specific index
31
+ book.add_sheet("New first", index=0)
32
+
33
+ book.finalize(to_file="_tests/simple.xlsx")
34
+
35
+ book = load_workbook(filename="_tests/simple.xlsx")
36
+ assert len(book.worksheets) == 3
37
+
38
+ sheet1, sheet2, sheet3 = book.worksheets
39
+ assert sheet1.title == "New first"
40
+ assert sheet2.title == "People"
41
+ assert sheet3.title == "Empty"
42
+
43
+ self.assertExcelSheet(sheet1, [])
44
+ self.assertExcelSheet(sheet2, [("Name", "Email"), ("Jim", "jim@acme.com"), ("Bob", "bob@acme.com")])
45
+ self.assertExcelSheet(sheet3, [])
46
+
47
+ def test_cell_types(self):
48
+ d1 = datetime(2013, 1, 1, 12, 0, 0)
49
+
50
+ book = XLSXBook()
51
+ sheet1 = book.add_sheet("Test")
52
+ sheet1.append_row("str", True, False, 3, 1.23, d1)
53
+
54
+ # try to write a cell value with an unsupported type
55
+ with pytest.raises(ValueError):
56
+ sheet1.append_row(timedelta(days=1))
57
+
58
+ book.finalize(to_file="_tests/types.xlsx")
59
+
60
+ book = load_workbook(filename="_tests/types.xlsx")
61
+ self.assertExcelSheet(book.worksheets[0], [("str", True, False, 3, 1.23, d1)])
62
+
63
+ def test_escaping(self):
64
+ book = XLSXBook()
65
+ sheet1 = book.add_sheet("Test")
66
+ sheet1.append_row('< & > " ! =')
67
+ book.finalize(to_file="_tests/escaped.xlsx")
68
+
69
+ book = load_workbook(filename="_tests/escaped.xlsx")
70
+ self.assertExcelSheet(book.worksheets[0], [('< & > " ! =',)])
71
+
72
+ def test_sheet_limits(self):
73
+ book = XLSXBook()
74
+ sheet1 = book.add_sheet("Sheet1")
75
+
76
+ # try to add row with too many columns
77
+ column = ["x"] * 20000
78
+ with pytest.raises(ValueError):
79
+ sheet1.append_row(*column)
80
+
81
+ # try to add more rows than allowed
82
+ with patch("xlsxlite.writer.XLSXSheet.MAX_ROWS", 3):
83
+ sheet1.append_row("x")
84
+ sheet1.append_row("x")
85
+ sheet1.append_row("x")
86
+
87
+ with pytest.raises(ValueError):
88
+ sheet1.append_row("x")
xlsxlite/utils.py ADDED
@@ -0,0 +1,14 @@
1
+ from datetime import datetime
2
+
3
+
4
+ def datetime_to_serial(dt):
5
+ """
6
+ Converts the given datetime to the Excel serial format
7
+ """
8
+ if dt.tzinfo:
9
+ raise ValueError("Doesn't support datetimes with timezones")
10
+
11
+ temp = datetime(1899, 12, 30)
12
+ delta = dt - temp
13
+
14
+ return delta.days + (float(delta.seconds) + float(delta.microseconds) / 1E6) / (60 * 60 * 24)
xlsxlite/writer.py ADDED
@@ -0,0 +1,278 @@
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ import xml.etree.ElementTree as ET
5
+ import zipfile
6
+ from datetime import datetime
7
+ from xml.sax.saxutils import escape
8
+
9
+ from .utils import datetime_to_serial
10
+
11
+ XML_HEADER = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n"""
12
+
13
+ WORKBOOK_HEADER = """<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">"""
14
+ WORKSHEET_HEADER = """<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">"""
15
+
16
+ MINIMAL_STYLESHEET = """<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
17
+ <numFmts count="1">
18
+ <numFmt formatCode="yyyy-mm-dd h:mm:ss" numFmtId="164"/>
19
+ </numFmts>
20
+ <fonts count="1">
21
+ <font>
22
+ <name val="Calibri"/>
23
+ <family val="2"/>
24
+ <color theme="1"/>
25
+ <sz val="11"/>
26
+ <scheme val="minor"/>
27
+ </font>
28
+ </fonts>
29
+ <fills count="2">
30
+ <fill><patternFill/></fill>
31
+ <fill><patternFill patternType="gray125"/></fill>
32
+ </fills>
33
+ <borders count="1">
34
+ <border>
35
+ <left/>
36
+ <right/>
37
+ <top/>
38
+ <bottom/>
39
+ <diagonal/>
40
+ </border>
41
+ </borders>
42
+ <cellStyleXfs count="1">
43
+ <xf borderId="0" fillId="0" fontId="0" numFmtId="0"/>
44
+ </cellStyleXfs>
45
+ <cellXfs count="2">
46
+ <xf borderId="0" fillId="0" fontId="0" numFmtId="0" pivotButton="0" quotePrefix="0" xfId="0"/>
47
+ <xf borderId="0" fillId="0" fontId="0" numFmtId="164" pivotButton="0" quotePrefix="0" xfId="0"/>
48
+ </cellXfs>
49
+ <cellStyles count="1">
50
+ <cellStyle builtinId="0" hidden="0" name="Normal" xfId="0"/>
51
+ </cellStyles>
52
+ </styleSheet>"""
53
+
54
+ # use a nice big 1MB I/O buffer for the worksheet files
55
+ WORKSHEET_IO_BUFFER = 1048576
56
+
57
+
58
+ class XLSXSheet:
59
+ """
60
+ A worksheet within a XLSX workbook
61
+ """
62
+
63
+ MAX_ROWS = 1048576
64
+ MAX_COLS = 16384
65
+
66
+ def __init__(self, _id, name, path):
67
+ self.id = _id
68
+ self.name = name
69
+ self.path = path
70
+ self.relationshipId = f"rId{_id}"
71
+
72
+ self.num_rows = 0
73
+
74
+ self.file = open(path, "w", encoding="utf-8", buffering=WORKSHEET_IO_BUFFER)
75
+ self.file.write(XML_HEADER)
76
+ self.file.write(WORKSHEET_HEADER)
77
+ self.file.write("<sheetData>")
78
+
79
+ def append_row(self, *columns):
80
+ """
81
+ Appends a new row to this sheet
82
+ """
83
+ if len(columns) > self.MAX_COLS:
84
+ raise ValueError(f"rows can have a maximum of {self.MAX_COLS} columns")
85
+
86
+ if self.num_rows >= self.MAX_ROWS:
87
+ raise ValueError(f"sheet already has the maximum of {self.MAX_ROWS} rows")
88
+
89
+ row = "<row>"
90
+ for val in columns:
91
+ if isinstance(val, str):
92
+ row += f'<c t="inlineStr"><is><t>{escape(val)}</t></is></c>'
93
+ elif isinstance(val, bool):
94
+ row += f'<c t="b"><v>{int(val)}</v></c>'
95
+ elif isinstance(val, (int, float)):
96
+ row += f'<c t="n"><v>{str(val)}</v></c>'
97
+ elif isinstance(val, datetime):
98
+ row += f'<c t="n" s="1"><v>{datetime_to_serial(val)}</v></c>'
99
+ else:
100
+ raise ValueError(f"Unsupported type in column data: {type(val)}")
101
+
102
+ row += "</row>"
103
+
104
+ self.file.write(row)
105
+ self.num_rows += 1
106
+
107
+ def finalize(self):
108
+ """
109
+ Finalizes this sheet so that its XML file is valid
110
+ """
111
+ self.file.write("</sheetData></worksheet>")
112
+ self.file.close()
113
+
114
+
115
+ class XLSXBook:
116
+ """
117
+ An XLSX workbook
118
+ """
119
+
120
+ def __init__(self):
121
+ self.base_dir = tempfile.mkdtemp()
122
+ self.app_dir = os.path.join(self.base_dir, "xl")
123
+ self.sheets = []
124
+
125
+ os.mkdir(self.app_dir)
126
+ os.mkdir(os.path.join(self.app_dir, "worksheets"))
127
+
128
+ def add_sheet(self, name, index=-1):
129
+ """
130
+ Adds a new worksheet to this workbook with the given name
131
+ """
132
+ _id = str(len(self.sheets) + 1)
133
+ path = os.path.join(self.app_dir, f"worksheets/sheet{_id}.xml")
134
+ sheet = XLSXSheet(_id, name, path)
135
+
136
+ if index < 0:
137
+ index = len(self.sheets)
138
+
139
+ self.sheets.insert(index, sheet)
140
+ return sheet
141
+
142
+ def _create_content_types(self):
143
+ types = ET.Element("Types", {"xmlns": "http://schemas.openxmlformats.org/package/2006/content-types"})
144
+ ET.SubElement(
145
+ types,
146
+ "Default",
147
+ {"Extension": "rels", "ContentType": "application/vnd.openxmlformats-package.relationships+xml"},
148
+ )
149
+ ET.SubElement(types, "Default", {"Extension": "xml", "ContentType": "application/xml"})
150
+ ET.SubElement(
151
+ types,
152
+ "Override",
153
+ {
154
+ "PartName": "/xl/styles.xml",
155
+ "ContentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
156
+ },
157
+ )
158
+ ET.SubElement(
159
+ types,
160
+ "Override",
161
+ {
162
+ "PartName": "/xl/workbook.xml",
163
+ "ContentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
164
+ },
165
+ )
166
+
167
+ for sheet in self.sheets:
168
+ rel_path = sheet.path[len(self.base_dir) :]
169
+ ET.SubElement(
170
+ types,
171
+ "Override",
172
+ {
173
+ "PartName": rel_path,
174
+ "ContentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
175
+ },
176
+ )
177
+
178
+ with open(os.path.join(self.base_dir, "[Content_Types].xml"), "w", encoding="utf-8") as f:
179
+ f.write(XML_HEADER)
180
+ f.write(ET.tostring(types, encoding="unicode"))
181
+
182
+ def _create_root_rels(self):
183
+ os.mkdir(os.path.join(self.base_dir, "_rels"))
184
+
185
+ relationships = ET.Element(
186
+ "Relationships", {"xmlns": "http://schemas.openxmlformats.org/package/2006/relationships"}
187
+ )
188
+ ET.SubElement(
189
+ relationships,
190
+ "Relationship",
191
+ {
192
+ "Id": "rId1",
193
+ "Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
194
+ "Target": "xl/workbook.xml",
195
+ },
196
+ )
197
+
198
+ with open(os.path.join(self.base_dir, "_rels/.rels"), "w", encoding="utf-8") as f:
199
+ f.write(XML_HEADER)
200
+ f.write(ET.tostring(relationships, encoding="unicode"))
201
+
202
+ def _create_app_rels(self):
203
+ os.mkdir(os.path.join(self.app_dir, "_rels"))
204
+
205
+ relationships = ET.Element(
206
+ "Relationships", {"xmlns": "http://schemas.openxmlformats.org/package/2006/relationships"}
207
+ )
208
+ for sheet in self.sheets:
209
+ rel_path = os.path.relpath(sheet.path, start=self.app_dir)
210
+
211
+ ET.SubElement(
212
+ relationships,
213
+ "Relationship",
214
+ {
215
+ "Id": sheet.relationshipId,
216
+ "Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
217
+ "Target": rel_path,
218
+ },
219
+ )
220
+
221
+ ET.SubElement(
222
+ relationships,
223
+ "Relationship",
224
+ {
225
+ "Id": "rIdStyles",
226
+ "Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
227
+ "Target": "styles.xml",
228
+ },
229
+ )
230
+
231
+ with open(os.path.join(self.app_dir, "_rels/workbook.xml.rels"), "w", encoding="utf-8") as f:
232
+ f.write(XML_HEADER)
233
+ f.write(ET.tostring(relationships, encoding="unicode"))
234
+
235
+ def _create_styles(self):
236
+ with open(os.path.join(self.app_dir, "styles.xml"), "w", encoding="utf-8") as f:
237
+ f.write(XML_HEADER)
238
+ f.write(MINIMAL_STYLESHEET)
239
+
240
+ def _create_workbook(self):
241
+ sheets = ET.Element("sheets")
242
+ for sheet in self.sheets:
243
+ ET.SubElement(sheets, "sheet", {"name": sheet.name, "sheetId": sheet.id, "r:id": sheet.relationshipId})
244
+
245
+ with open(os.path.join(self.base_dir, "xl/workbook.xml"), "w", encoding="utf-8") as f:
246
+ f.write(XML_HEADER)
247
+ f.write(WORKBOOK_HEADER)
248
+ f.write(ET.tostring(sheets, encoding="unicode"))
249
+ f.write("</workbook>")
250
+
251
+ def _archive_dir(self, to_file):
252
+ archive = zipfile.ZipFile(to_file, "w", zipfile.ZIP_DEFLATED)
253
+
254
+ for root, dirs, files in os.walk(self.base_dir):
255
+ for file in files:
256
+ rel_path = os.path.relpath(os.path.join(root, file), start=self.base_dir)
257
+ archive.write(os.path.join(root, file), arcname=rel_path)
258
+
259
+ archive.close()
260
+
261
+ def finalize(self, to_file, remove_dir=True):
262
+ # must have at least one sheet
263
+ if not self.sheets:
264
+ self.add_sheet("Sheet1")
265
+
266
+ self._create_content_types()
267
+ self._create_root_rels()
268
+ self._create_app_rels()
269
+ self._create_styles()
270
+ self._create_workbook()
271
+
272
+ for sheet in self.sheets:
273
+ sheet.finalize()
274
+
275
+ self._archive_dir(to_file)
276
+
277
+ if remove_dir:
278
+ shutil.rmtree(self.base_dir)
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020-2022 TextIt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.3
2
+ Name: xlsxlite
3
+ Version: 1.0.0
4
+ Summary: Lightweight XLSX writer with emphasis on minimizing memory usage.
5
+ License: MIT
6
+ Author: TextIt
7
+ Author-email: code@textit.com
8
+ Requires-Python: >=3.10
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Project-URL: repository, http://github.com/nyaruka/xlsxlite
@@ -0,0 +1,12 @@
1
+ xlsxlite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ xlsxlite/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ xlsxlite/test/base.py,sha256=eLVJSX2Y6hdSMhV6HTOe8rGXa6QGzQ-FKUOkNRkk9F4,1708
4
+ xlsxlite/test/test_perf.py,sha256=T7n7CYzISpictXCj5euSA7A1RnuZu60F2-j6wQ71yq0,1965
5
+ xlsxlite/test/test_utils.py,sha256=0dTObD-BXcfOM8-eUjFDayQBLXgplg8jo6237dvu6Xs,447
6
+ xlsxlite/test/test_writer.py,sha256=L4SWbnNkarIL4dV3Prs7VgN3u983E5htaNIh7ZXv46Q,2857
7
+ xlsxlite/utils.py,sha256=5S36PdU-FnCQ3lY9BkCE4whMMxonYrRweuEflxHacC0,378
8
+ xlsxlite/writer.py,sha256=1TjFfBBH-DB3TknNWwVmGdWfUGChIleIEF3UNKBwHik,9345
9
+ xlsxlite-1.0.0.dist-info/LICENSE,sha256=noCRz7EAyVA_li4teSPmicKInOy2mgCDR6u4ftxfVgo,1078
10
+ xlsxlite-1.0.0.dist-info/METADATA,sha256=DitW6Fht2VeVkmO7ECH2hUcpMsbzl8dYCCAQ0g2EaEQ,454
11
+ xlsxlite-1.0.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
12
+ xlsxlite-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.0.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any