excelreader-native 7.0.0__py3-none-macosx_12_0_x86_64.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.
- excelreader/__init__.py +44 -0
- excelreader/_lib/.gitkeep +0 -0
- excelreader/_lib/ExcelReader.Native.dylib +0 -0
- excelreader/_native.py +581 -0
- excelreader/arrow_stream.py +124 -0
- excelreader/py.typed +0 -0
- excelreader/reader.py +619 -0
- excelreader/sources.py +105 -0
- excelreader/types.py +244 -0
- excelreader/writer.py +417 -0
- excelreader/writer_handle.py +205 -0
- excelreader_native-7.0.0.dist-info/METADATA +606 -0
- excelreader_native-7.0.0.dist-info/RECORD +14 -0
- excelreader_native-7.0.0.dist-info/WHEEL +4 -0
excelreader/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Python bindings for ExcelReader."""
|
|
2
|
+
|
|
3
|
+
from excelreader.reader import Sheet, Sheets, Workbook, open_workbook
|
|
4
|
+
from excelreader.sources import Source
|
|
5
|
+
from excelreader.types import (
|
|
6
|
+
Cell,
|
|
7
|
+
CellType,
|
|
8
|
+
ColumnSpec,
|
|
9
|
+
ColumnType,
|
|
10
|
+
ExcelReaderError,
|
|
11
|
+
OpenOptions,
|
|
12
|
+
PasswordIncorrectError,
|
|
13
|
+
PasswordRequiredError,
|
|
14
|
+
SheetVisibility,
|
|
15
|
+
StringColumn,
|
|
16
|
+
TypedTable,
|
|
17
|
+
WriteOptions,
|
|
18
|
+
)
|
|
19
|
+
from excelreader.writer import encrypt_package, write_workbook
|
|
20
|
+
from excelreader.writer_handle import SheetWriter, open_writer
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"Cell",
|
|
24
|
+
"CellType",
|
|
25
|
+
"ColumnSpec",
|
|
26
|
+
"ColumnType",
|
|
27
|
+
"ExcelReaderError",
|
|
28
|
+
"OpenOptions",
|
|
29
|
+
"PasswordIncorrectError",
|
|
30
|
+
"PasswordRequiredError",
|
|
31
|
+
"Sheet",
|
|
32
|
+
"SheetVisibility",
|
|
33
|
+
"SheetWriter",
|
|
34
|
+
"Sheets",
|
|
35
|
+
"Source",
|
|
36
|
+
"StringColumn",
|
|
37
|
+
"TypedTable",
|
|
38
|
+
"Workbook",
|
|
39
|
+
"WriteOptions",
|
|
40
|
+
"encrypt_package",
|
|
41
|
+
"open_workbook",
|
|
42
|
+
"open_writer",
|
|
43
|
+
"write_workbook",
|
|
44
|
+
]
|
|
File without changes
|
|
Binary file
|
excelreader/_native.py
ADDED
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
"""ctypes binding to the ExcelReader NativeAOT shared library.
|
|
2
|
+
|
|
3
|
+
Everything here mirrors src/ExcelReader.Native/include/excelreader.h. If you change one, change both.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import ctypes
|
|
9
|
+
import os
|
|
10
|
+
import platform
|
|
11
|
+
import threading
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
from functools import lru_cache
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from excelreader.types import OpenOptions, WriteOptions
|
|
19
|
+
|
|
20
|
+
XL_OK = 0
|
|
21
|
+
XL_EOF = -1
|
|
22
|
+
XL_BUFFER_TOO_SMALL = -2
|
|
23
|
+
XL_INVALID_HANDLE = -3
|
|
24
|
+
XL_INVALID_ARGUMENT = -4
|
|
25
|
+
XL_ERROR = -5
|
|
26
|
+
XL_STATUS_PASSWORD_REQUIRED = -6
|
|
27
|
+
XL_STATUS_PASSWORD_INCORRECT = -7
|
|
28
|
+
|
|
29
|
+
XL_ABI_VERSION = 6
|
|
30
|
+
|
|
31
|
+
XL_SHEET_VISIBLE = 0
|
|
32
|
+
XL_SHEET_HIDDEN = 1
|
|
33
|
+
XL_SHEET_VERY_HIDDEN = 2
|
|
34
|
+
|
|
35
|
+
XL_FORMAT_AUTO = 0
|
|
36
|
+
XL_FORMAT_XLS = 1
|
|
37
|
+
XL_FORMAT_XLSX = 2
|
|
38
|
+
XL_FORMAT_XLSB = 3
|
|
39
|
+
XL_FORMAT_CSV = 4
|
|
40
|
+
XL_INFER_PARSE_TEXT = 1
|
|
41
|
+
FORMATS = {
|
|
42
|
+
"auto": XL_FORMAT_AUTO,
|
|
43
|
+
"xls": XL_FORMAT_XLS,
|
|
44
|
+
"xlsx": XL_FORMAT_XLSX,
|
|
45
|
+
"xlsb": XL_FORMAT_XLSB,
|
|
46
|
+
"csv": XL_FORMAT_CSV,
|
|
47
|
+
}
|
|
48
|
+
WRITE_FORMATS = {name: value for name, value in FORMATS.items() if name != "auto"}
|
|
49
|
+
|
|
50
|
+
XL_OPT_DEFAULT = 0
|
|
51
|
+
XL_OPT_FALSE = 1
|
|
52
|
+
XL_OPT_TRUE = 2
|
|
53
|
+
|
|
54
|
+
XL_T_STRING = 0
|
|
55
|
+
XL_T_I64 = 1
|
|
56
|
+
XL_T_F64 = 2
|
|
57
|
+
XL_T_BOOL = 3
|
|
58
|
+
XL_T_DATE = 4
|
|
59
|
+
XL_T_TIME = 5
|
|
60
|
+
XL_T_TIMESTAMP = 6
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class NativeColumnSpec(ctypes.Structure):
|
|
64
|
+
"""Mirrors xl_column_spec. `names`/`name_lens`/`name_count` may be left NULL/NULL/0 to resolve by
|
|
65
|
+
`index` instead. Build one with `column_spec_by_name`/`column_spec_by_names`, never directly —
|
|
66
|
+
the `names`/`name_lens` pointers must stay alive as long as the struct is in use, and those
|
|
67
|
+
helpers keep the backing buffers alive via ctypes' `_objects` mechanism."""
|
|
68
|
+
|
|
69
|
+
_fields_ = [
|
|
70
|
+
("names", ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8))),
|
|
71
|
+
("name_lens", ctypes.POINTER(ctypes.c_int32)),
|
|
72
|
+
("name_count", ctypes.c_int32),
|
|
73
|
+
("index", ctypes.c_int32),
|
|
74
|
+
("type", ctypes.c_int32),
|
|
75
|
+
("nullable", ctypes.c_int32),
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def column_spec_by_names(names: Sequence[str], type_: int, *, nullable: bool = False) -> NativeColumnSpec:
|
|
80
|
+
encoded = [name.encode("utf-8") for name in names]
|
|
81
|
+
buffers = [ctypes.create_string_buffer(e, len(e)) for e in encoded]
|
|
82
|
+
name_ptrs = (ctypes.POINTER(ctypes.c_uint8) * len(buffers))(
|
|
83
|
+
*(ctypes.cast(b, ctypes.POINTER(ctypes.c_uint8)) for b in buffers)
|
|
84
|
+
)
|
|
85
|
+
name_lens = (ctypes.c_int32 * len(encoded))(*(len(e) for e in encoded))
|
|
86
|
+
spec = NativeColumnSpec(
|
|
87
|
+
names=ctypes.cast(name_ptrs, ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8))),
|
|
88
|
+
name_lens=name_lens,
|
|
89
|
+
name_count=len(encoded),
|
|
90
|
+
index=0,
|
|
91
|
+
type=type_,
|
|
92
|
+
nullable=int(nullable),
|
|
93
|
+
)
|
|
94
|
+
spec._name_storage = (buffers, name_ptrs, name_lens) # type: ignore[attr-defined]
|
|
95
|
+
return spec
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def column_spec_by_name(name: str, type_: int, *, nullable: bool = False) -> NativeColumnSpec:
|
|
99
|
+
return column_spec_by_names([name], type_, nullable=nullable)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def column_spec_by_index(index: int, type_: int, *, nullable: bool = False) -> NativeColumnSpec:
|
|
103
|
+
return NativeColumnSpec(
|
|
104
|
+
names=None, name_lens=None, name_count=0, index=index, type=type_, nullable=int(nullable)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class NativeInferredColumnSpec(NativeColumnSpec):
|
|
109
|
+
"""The OUTPUT direction of the same xl_column_spec (xl_infer_schema fills these in).
|
|
110
|
+
|
|
111
|
+
Layout is inherited, not restated: it is literally the same C struct, and a second copy of the
|
|
112
|
+
field list is a second place for it to drift from the header. Only the ownership rules differ,
|
|
113
|
+
which is why this carries its own name.
|
|
114
|
+
|
|
115
|
+
Always carries `name_count` 0 or 1 — inference never guesses more than one candidate name per
|
|
116
|
+
column. Unlike `column_spec_by_name`'s buffers, `names[0]` here is a raw pointer with no
|
|
117
|
+
guaranteed NUL terminator, so decode it with `ctypes.string_at(names[0], name_lens[0])`.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class NativeInferredSchema(ctypes.Structure):
|
|
122
|
+
"""Mirrors xl_inferred_schema. `columns` is a native-owned array of `column_count` values, freed
|
|
123
|
+
(along with each column's own `name`) by `xl_free_schema`."""
|
|
124
|
+
|
|
125
|
+
_fields_ = [
|
|
126
|
+
("columns", ctypes.POINTER(NativeInferredColumnSpec)),
|
|
127
|
+
("column_count", ctypes.c_int32),
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class NativeColumn(ctypes.Structure):
|
|
132
|
+
"""Mirrors xl_column. `data`/`data_len` are only meaningful for XL_T_STRING columns; `values` is
|
|
133
|
+
always the ONE allocation this column owns directly (see xl_column's doc comment in the header —
|
|
134
|
+
`data` is an interior pointer into `values` for strings, never a separate allocation)."""
|
|
135
|
+
|
|
136
|
+
_fields_ = [
|
|
137
|
+
("type", ctypes.c_int32),
|
|
138
|
+
("length", ctypes.c_int64),
|
|
139
|
+
("values", ctypes.c_void_p),
|
|
140
|
+
("validity", ctypes.POINTER(ctypes.c_uint8)),
|
|
141
|
+
("data", ctypes.POINTER(ctypes.c_uint8)),
|
|
142
|
+
("data_len", ctypes.c_int64),
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class NativeTable(ctypes.Structure):
|
|
147
|
+
_fields_ = [
|
|
148
|
+
("column_count", ctypes.c_int32),
|
|
149
|
+
("row_count", ctypes.c_int64),
|
|
150
|
+
("columns", ctypes.POINTER(NativeColumn)),
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ArrowSchema(ctypes.Structure):
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class ArrowArray(ctypes.Structure):
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
ArrowSchema._fields_ = [
|
|
163
|
+
("format", ctypes.c_char_p),
|
|
164
|
+
("name", ctypes.c_char_p),
|
|
165
|
+
("metadata", ctypes.c_char_p),
|
|
166
|
+
("flags", ctypes.c_int64),
|
|
167
|
+
("n_children", ctypes.c_int64),
|
|
168
|
+
("children", ctypes.POINTER(ctypes.POINTER(ArrowSchema))),
|
|
169
|
+
("dictionary", ctypes.POINTER(ArrowSchema)),
|
|
170
|
+
("release", ctypes.c_void_p),
|
|
171
|
+
("private_data", ctypes.c_void_p),
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
ArrowArray._fields_ = [
|
|
175
|
+
("length", ctypes.c_int64),
|
|
176
|
+
("null_count", ctypes.c_int64),
|
|
177
|
+
("offset", ctypes.c_int64),
|
|
178
|
+
("n_buffers", ctypes.c_int64),
|
|
179
|
+
("n_children", ctypes.c_int64),
|
|
180
|
+
("buffers", ctypes.POINTER(ctypes.c_void_p)),
|
|
181
|
+
("children", ctypes.POINTER(ctypes.POINTER(ArrowArray))),
|
|
182
|
+
("dictionary", ctypes.POINTER(ArrowArray)),
|
|
183
|
+
("release", ctypes.c_void_p),
|
|
184
|
+
("private_data", ctypes.c_void_p),
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
ARROW_FLAG_NULLABLE = 2
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class ArrowArrayStream(ctypes.Structure):
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
_ArrowStreamGetSchema = ctypes.CFUNCTYPE(
|
|
195
|
+
ctypes.c_int, ctypes.POINTER(ArrowArrayStream), ctypes.POINTER(ArrowSchema)
|
|
196
|
+
)
|
|
197
|
+
_ArrowStreamGetNext = ctypes.CFUNCTYPE(
|
|
198
|
+
ctypes.c_int, ctypes.POINTER(ArrowArrayStream), ctypes.POINTER(ArrowArray)
|
|
199
|
+
)
|
|
200
|
+
_ArrowStreamGetLastError = ctypes.CFUNCTYPE(ctypes.c_char_p, ctypes.POINTER(ArrowArrayStream))
|
|
201
|
+
_ArrowStreamRelease = ctypes.CFUNCTYPE(None, ctypes.POINTER(ArrowArrayStream))
|
|
202
|
+
|
|
203
|
+
ArrowArrayStream._fields_ = [
|
|
204
|
+
("get_schema", _ArrowStreamGetSchema),
|
|
205
|
+
("get_next", _ArrowStreamGetNext),
|
|
206
|
+
("get_last_error", _ArrowStreamGetLastError),
|
|
207
|
+
("release", _ArrowStreamRelease),
|
|
208
|
+
("private_data", ctypes.c_void_p),
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class NativeBuffer(ctypes.Structure):
|
|
213
|
+
"""Mirrors xl_buffer."""
|
|
214
|
+
|
|
215
|
+
_fields_ = [
|
|
216
|
+
("data", ctypes.POINTER(ctypes.c_uint8)),
|
|
217
|
+
("len", ctypes.c_int64),
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class NativeOpenOptions(ctypes.Structure):
|
|
222
|
+
"""Mirrors xl_open_options. `struct_size` is set for you by `default_open_options()`; every other
|
|
223
|
+
numeric field is 0 (use the library default), and every XL_OPT_* field starts at XL_OPT_DEFAULT."""
|
|
224
|
+
|
|
225
|
+
_fields_ = [
|
|
226
|
+
("struct_size", ctypes.c_int32),
|
|
227
|
+
("csv_sniff_dialect", ctypes.c_int32),
|
|
228
|
+
("csv_delimiter", ctypes.c_int32),
|
|
229
|
+
("csv_quote", ctypes.c_int32),
|
|
230
|
+
("csv_detect_bom", ctypes.c_int32),
|
|
231
|
+
("csv_max_cell_bytes", ctypes.c_int32),
|
|
232
|
+
("csv_intern_strings", ctypes.c_int32),
|
|
233
|
+
("max_total_decompressed_bytes", ctypes.c_int64),
|
|
234
|
+
("max_cell_bytes", ctypes.c_int32),
|
|
235
|
+
("max_shared_string_bytes", ctypes.c_int64),
|
|
236
|
+
("max_zip_entries", ctypes.c_int32),
|
|
237
|
+
("prefetch_decompression", ctypes.c_int32),
|
|
238
|
+
("intern_strings", ctypes.c_int32),
|
|
239
|
+
("password", ctypes.POINTER(ctypes.c_uint8)),
|
|
240
|
+
("password_len", ctypes.c_int32),
|
|
241
|
+
("source_cache_bytes", ctypes.c_int64),
|
|
242
|
+
("max_buffered_bytes", ctypes.c_int64),
|
|
243
|
+
("source_block_size", ctypes.c_int32),
|
|
244
|
+
("verify_zip_checksums", ctypes.c_int32),
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
SourceReadAt = ctypes.CFUNCTYPE(ctypes.c_int64, ctypes.c_void_p, ctypes.c_int64, ctypes.c_void_p, ctypes.c_int64)
|
|
249
|
+
StreamRead = ctypes.CFUNCTYPE(ctypes.c_int64, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int64)
|
|
250
|
+
SourceRelease = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class NativeSource(ctypes.Structure):
|
|
254
|
+
"""Mirrors xl_source."""
|
|
255
|
+
|
|
256
|
+
_fields_ = [
|
|
257
|
+
("struct_size", ctypes.c_int32),
|
|
258
|
+
("user_data", ctypes.c_void_p),
|
|
259
|
+
("length", ctypes.c_int64),
|
|
260
|
+
("read_at", SourceReadAt),
|
|
261
|
+
("release", SourceRelease),
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class NativeStream(ctypes.Structure):
|
|
266
|
+
"""Mirrors xl_stream."""
|
|
267
|
+
|
|
268
|
+
_fields_ = [
|
|
269
|
+
("struct_size", ctypes.c_int32),
|
|
270
|
+
("user_data", ctypes.c_void_p),
|
|
271
|
+
("read", StreamRead),
|
|
272
|
+
("release", SourceRelease),
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def default_open_options() -> NativeOpenOptions:
|
|
277
|
+
"""A NativeOpenOptions with every field at its "use the library default" value and struct_size
|
|
278
|
+
filled in — start from this rather than constructing NativeOpenOptions() directly, since the raw
|
|
279
|
+
zero-value for struct_size is never valid."""
|
|
280
|
+
options = NativeOpenOptions()
|
|
281
|
+
options.struct_size = ctypes.sizeof(NativeOpenOptions)
|
|
282
|
+
return options
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _opt_state(value: bool | None) -> int:
|
|
286
|
+
if value is None:
|
|
287
|
+
return XL_OPT_DEFAULT
|
|
288
|
+
return XL_OPT_TRUE if value else XL_OPT_FALSE
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _opt_number(value: int | None) -> int:
|
|
292
|
+
return 0 if value is None else value
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def to_native_open_options(options: OpenOptions) -> NativeOpenOptions:
|
|
296
|
+
"""Converts a public `OpenOptions` into the raw ABI struct.
|
|
297
|
+
|
|
298
|
+
This is where the public API's single "None means default" convention splits back into the two
|
|
299
|
+
the ABI uses: 0 for an unset number, XL_OPT_DEFAULT for an unset boolean. Note the numeric
|
|
300
|
+
fields cannot express a deliberate 0 — the ABI spends that value on "default" — but no field
|
|
301
|
+
here has a meaningful 0 (a zero delimiter, or a zero-byte cell limit, is not a setting).
|
|
302
|
+
|
|
303
|
+
Values are passed through unvalidated on purpose: the native side owns the real bounds and
|
|
304
|
+
reports a rejection through xl_last_error, so checking them here too would give those bounds a
|
|
305
|
+
second place to drift from.
|
|
306
|
+
"""
|
|
307
|
+
raw = default_open_options()
|
|
308
|
+
raw.csv_sniff_dialect = _opt_state(options.csv_sniff_dialect)
|
|
309
|
+
raw.csv_detect_bom = _opt_state(options.csv_detect_bom)
|
|
310
|
+
raw.prefetch_decompression = _opt_state(options.prefetch_decompression)
|
|
311
|
+
raw.intern_strings = _opt_state(options.intern_strings)
|
|
312
|
+
raw.verify_zip_checksums = _opt_state(options.verify_zip_checksums)
|
|
313
|
+
raw.csv_delimiter = _opt_number(options.csv_delimiter)
|
|
314
|
+
raw.csv_quote = _opt_number(options.csv_quote)
|
|
315
|
+
# The ABI's csv_* copies win over these for a CSV even when unset, so send the same value to both.
|
|
316
|
+
raw.csv_max_cell_bytes = _opt_number(options.max_cell_bytes)
|
|
317
|
+
raw.csv_intern_strings = raw.intern_strings
|
|
318
|
+
raw.max_total_decompressed_bytes = _opt_number(options.max_total_decompressed_bytes)
|
|
319
|
+
raw.max_cell_bytes = _opt_number(options.max_cell_bytes)
|
|
320
|
+
raw.max_shared_string_bytes = _opt_number(options.max_shared_string_bytes)
|
|
321
|
+
raw.max_zip_entries = _opt_number(options.max_zip_entries)
|
|
322
|
+
raw.source_cache_bytes = _opt_number(options.source_cache_bytes)
|
|
323
|
+
raw.max_buffered_bytes = _opt_number(options.max_buffered_bytes)
|
|
324
|
+
raw.source_block_size = _opt_number(options.source_block_size)
|
|
325
|
+
return raw
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class NativeWriteOptions(ctypes.Structure):
|
|
329
|
+
"""Mirrors xl_write_options. `struct_size` is set for you by `default_write_options()`; every other
|
|
330
|
+
numeric field is 0 (use the library default), and every XL_OPT_* field starts at XL_OPT_DEFAULT."""
|
|
331
|
+
|
|
332
|
+
_fields_ = [
|
|
333
|
+
("struct_size", ctypes.c_int32),
|
|
334
|
+
("sheet_name_len", ctypes.c_int32),
|
|
335
|
+
("sheet_name", ctypes.c_char_p),
|
|
336
|
+
("csv_delimiter", ctypes.c_int32),
|
|
337
|
+
("csv_quote", ctypes.c_int32),
|
|
338
|
+
("date1904", ctypes.c_int32),
|
|
339
|
+
("use_shared_strings", ctypes.c_int32),
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def default_write_options() -> NativeWriteOptions:
|
|
344
|
+
"""A NativeWriteOptions with every field at its "use the library default" value and struct_size
|
|
345
|
+
filled in — start from this rather than constructing NativeWriteOptions() directly, since the raw
|
|
346
|
+
zero-value for struct_size is never valid."""
|
|
347
|
+
options = NativeWriteOptions()
|
|
348
|
+
options.struct_size = ctypes.sizeof(NativeWriteOptions)
|
|
349
|
+
return options
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def to_native_write_options(options: WriteOptions) -> NativeWriteOptions:
|
|
353
|
+
"""Converts a public `WriteOptions` into the raw ABI struct.
|
|
354
|
+
|
|
355
|
+
Same split as `to_native_open_options()`: the public API's single "None means default" convention
|
|
356
|
+
becomes 0 for an unset number and XL_OPT_DEFAULT for an unset boolean. Values are passed through
|
|
357
|
+
unvalidated on purpose — the native side owns the real bounds and reports a rejection through
|
|
358
|
+
xl_last_error, so checking them here too would give those bounds a second place to drift from.
|
|
359
|
+
"""
|
|
360
|
+
raw = default_write_options()
|
|
361
|
+
if options.sheet_name is not None:
|
|
362
|
+
encoded = options.sheet_name.encode("utf-8")
|
|
363
|
+
raw.sheet_name = encoded
|
|
364
|
+
raw.sheet_name_len = len(encoded)
|
|
365
|
+
raw.csv_delimiter = _opt_number(options.csv_delimiter)
|
|
366
|
+
raw.csv_quote = _opt_number(options.csv_quote)
|
|
367
|
+
raw.date1904 = _opt_state(options.date1904)
|
|
368
|
+
raw.use_shared_strings = _opt_state(options.use_shared_strings)
|
|
369
|
+
return raw
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
_LIB_NAMES = {
|
|
373
|
+
"Windows": "ExcelReader.Native.dll",
|
|
374
|
+
"Linux": "ExcelReader.Native.so",
|
|
375
|
+
"Darwin": "ExcelReader.Native.dylib",
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
def library_filename() -> str:
|
|
379
|
+
try:
|
|
380
|
+
return _LIB_NAMES[platform.system()]
|
|
381
|
+
except KeyError:
|
|
382
|
+
raise RuntimeError(f"unsupported platform: {platform.system()}") from None
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _candidate_paths() -> list[Path]:
|
|
386
|
+
override = os.environ.get("EXCELREADER_NATIVE_LIB")
|
|
387
|
+
if override:
|
|
388
|
+
return [Path(override)]
|
|
389
|
+
return [Path(__file__).resolve().parent / "_lib" / library_filename()]
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
_load_lock = threading.Lock()
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@lru_cache(maxsize=1)
|
|
396
|
+
def load_library() -> ctypes.CDLL:
|
|
397
|
+
with _load_lock:
|
|
398
|
+
for path in _candidate_paths():
|
|
399
|
+
if not path.exists():
|
|
400
|
+
continue
|
|
401
|
+
lib = _bind(ctypes.CDLL(str(path)))
|
|
402
|
+
version = lib.xl_abi_version()
|
|
403
|
+
if version != XL_ABI_VERSION:
|
|
404
|
+
raise RuntimeError(
|
|
405
|
+
f"{path} is ABI version {version}, but this package expects {XL_ABI_VERSION}. "
|
|
406
|
+
f"Rebuild the native library with python python/scripts/build_native.py."
|
|
407
|
+
)
|
|
408
|
+
_warm_up(lib)
|
|
409
|
+
return lib
|
|
410
|
+
raise RuntimeError(
|
|
411
|
+
f"{library_filename()} not found. Build it with:\n"
|
|
412
|
+
f" python python/scripts/build_native.py\n"
|
|
413
|
+
f"or point EXCELREADER_NATIVE_LIB at an existing binary."
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _warm_up(lib: ctypes.CDLL) -> None:
|
|
418
|
+
data = b"a,b\n1,2\n3,4\n"
|
|
419
|
+
workbook = ctypes.c_void_p()
|
|
420
|
+
if lib.xl_open_memory(data, len(data), XL_FORMAT_CSV, None, ctypes.byref(workbook)) != XL_OK:
|
|
421
|
+
return
|
|
422
|
+
try:
|
|
423
|
+
cursor = ctypes.c_void_p()
|
|
424
|
+
if lib.xl_rows_open(workbook, 0, ctypes.byref(cursor)) != XL_OK:
|
|
425
|
+
return
|
|
426
|
+
try:
|
|
427
|
+
written = ctypes.c_int32()
|
|
428
|
+
capacity = 16
|
|
429
|
+
buffer = ctypes.create_string_buffer(capacity)
|
|
430
|
+
while True:
|
|
431
|
+
status = lib.xl_rows_next(cursor, buffer, capacity, ctypes.byref(written))
|
|
432
|
+
if status == XL_BUFFER_TOO_SMALL:
|
|
433
|
+
capacity = written.value
|
|
434
|
+
buffer = ctypes.create_string_buffer(capacity)
|
|
435
|
+
elif status != XL_OK:
|
|
436
|
+
return
|
|
437
|
+
finally:
|
|
438
|
+
lib.xl_rows_close(cursor)
|
|
439
|
+
finally:
|
|
440
|
+
lib.xl_close(workbook)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _bind(lib: ctypes.CDLL) -> ctypes.CDLL:
|
|
444
|
+
c_int = ctypes.c_int32
|
|
445
|
+
p_int = ctypes.POINTER(ctypes.c_int32)
|
|
446
|
+
p_void = ctypes.c_void_p
|
|
447
|
+
pp_void = ctypes.POINTER(ctypes.c_void_p)
|
|
448
|
+
p_bytes = ctypes.c_char_p
|
|
449
|
+
|
|
450
|
+
p_open_options = ctypes.POINTER(NativeOpenOptions)
|
|
451
|
+
|
|
452
|
+
p_specs = ctypes.POINTER(NativeColumnSpec)
|
|
453
|
+
|
|
454
|
+
lib.xl_open_file.argtypes = [p_bytes, c_int, c_int, p_open_options, pp_void]
|
|
455
|
+
lib.xl_open_file.restype = c_int
|
|
456
|
+
lib.xl_open_memory.argtypes = [p_bytes, c_int, c_int, p_open_options, pp_void]
|
|
457
|
+
lib.xl_open_memory.restype = c_int
|
|
458
|
+
lib.xl_open_source.argtypes = [ctypes.POINTER(NativeSource), c_int, p_open_options, pp_void]
|
|
459
|
+
lib.xl_open_source.restype = c_int
|
|
460
|
+
lib.xl_open_stream.argtypes = [ctypes.POINTER(NativeStream), c_int, p_open_options, pp_void]
|
|
461
|
+
lib.xl_open_stream.restype = c_int
|
|
462
|
+
lib.xl_set_source_error.argtypes = [p_bytes, c_int]
|
|
463
|
+
lib.xl_set_source_error.restype = None
|
|
464
|
+
lib.xl_sheet_visibility_at.argtypes = [p_void, c_int, p_int]
|
|
465
|
+
lib.xl_sheet_visibility_at.restype = c_int
|
|
466
|
+
lib.xl_sheet_index.argtypes = [p_void, p_bytes, c_int, p_int]
|
|
467
|
+
lib.xl_sheet_index.restype = c_int
|
|
468
|
+
lib.xl_rows_open.argtypes = [p_void, c_int, pp_void]
|
|
469
|
+
lib.xl_rows_open.restype = c_int
|
|
470
|
+
lib.xl_rows_close.argtypes = [p_void]
|
|
471
|
+
lib.xl_rows_close.restype = c_int
|
|
472
|
+
lib.xl_rows_next.argtypes = [p_void, p_bytes, c_int, p_int]
|
|
473
|
+
lib.xl_rows_next.restype = c_int
|
|
474
|
+
lib.xl_parse_typed.argtypes = [p_void, c_int, p_specs, c_int, c_int, c_int, ctypes.POINTER(NativeTable)]
|
|
475
|
+
lib.xl_parse_typed.restype = c_int
|
|
476
|
+
lib.xl_typed_reader_open.argtypes = [p_void, c_int, p_specs, c_int, c_int, ctypes.c_int64, pp_void]
|
|
477
|
+
lib.xl_typed_reader_open.restype = c_int
|
|
478
|
+
lib.xl_parse_arrow.argtypes = [
|
|
479
|
+
p_void, c_int, p_specs, c_int, c_int, c_int, ctypes.POINTER(ArrowArray), ctypes.POINTER(ArrowSchema),
|
|
480
|
+
]
|
|
481
|
+
lib.xl_parse_arrow.restype = c_int
|
|
482
|
+
lib.xl_parse_arrow_stream.argtypes = [
|
|
483
|
+
p_void, c_int, p_specs, c_int, c_int, ctypes.c_int64, ctypes.POINTER(ArrowArrayStream),
|
|
484
|
+
]
|
|
485
|
+
lib.xl_parse_arrow_stream.restype = c_int
|
|
486
|
+
lib.xl_infer_schema.argtypes = [p_void, c_int, c_int, c_int, c_int, ctypes.POINTER(NativeInferredSchema)]
|
|
487
|
+
lib.xl_infer_schema.restype = c_int
|
|
488
|
+
lib.xl_close.argtypes = [p_void]
|
|
489
|
+
lib.xl_close.restype = c_int
|
|
490
|
+
lib.xl_sheet_count.argtypes = [p_void, p_int]
|
|
491
|
+
lib.xl_sheet_count.restype = c_int
|
|
492
|
+
lib.xl_sheet_name_at.argtypes = [p_void, c_int, p_bytes, c_int, p_int]
|
|
493
|
+
lib.xl_sheet_name_at.restype = c_int
|
|
494
|
+
lib.xl_is_date1904.argtypes = [p_void, p_int]
|
|
495
|
+
lib.xl_is_date1904.restype = c_int
|
|
496
|
+
lib.xl_last_error.argtypes = [p_bytes, c_int, p_int]
|
|
497
|
+
lib.xl_last_error.restype = c_int
|
|
498
|
+
lib.xl_last_error_ptr.argtypes = [p_int]
|
|
499
|
+
lib.xl_last_error_ptr.restype = ctypes.POINTER(ctypes.c_uint8)
|
|
500
|
+
lib.xl_free_table.argtypes = [ctypes.POINTER(NativeTable)]
|
|
501
|
+
lib.xl_free_table.restype = None
|
|
502
|
+
lib.xl_free_schema.argtypes = [ctypes.POINTER(NativeInferredSchema)]
|
|
503
|
+
lib.xl_free_schema.restype = None
|
|
504
|
+
lib.xl_typed_reader_next.argtypes = [p_void, ctypes.POINTER(NativeTable)]
|
|
505
|
+
lib.xl_typed_reader_next.restype = c_int
|
|
506
|
+
lib.xl_typed_reader_close.argtypes = [p_void]
|
|
507
|
+
lib.xl_typed_reader_close.restype = None
|
|
508
|
+
lib.xl_write_typed.argtypes = [
|
|
509
|
+
p_bytes, c_int, c_int,
|
|
510
|
+
ctypes.POINTER(NativeColumnSpec), ctypes.POINTER(NativeTable), ctypes.POINTER(NativeWriteOptions),
|
|
511
|
+
]
|
|
512
|
+
lib.xl_write_typed.restype = c_int
|
|
513
|
+
lib.xl_write_typed_to_memory.argtypes = [
|
|
514
|
+
ctypes.c_int32, ctypes.POINTER(NativeColumnSpec), ctypes.POINTER(NativeTable),
|
|
515
|
+
ctypes.POINTER(NativeWriteOptions), ctypes.POINTER(NativeBuffer),
|
|
516
|
+
]
|
|
517
|
+
lib.xl_write_typed_to_memory.restype = ctypes.c_int32
|
|
518
|
+
lib.xl_encrypt_package.argtypes = [p_bytes, c_int, p_bytes, c_int, p_bytes, c_int]
|
|
519
|
+
lib.xl_encrypt_package.restype = c_int
|
|
520
|
+
lib.xl_encrypt_package_to_memory.argtypes = [p_bytes, c_int, p_bytes, c_int, ctypes.POINTER(NativeBuffer)]
|
|
521
|
+
lib.xl_encrypt_package_to_memory.restype = c_int
|
|
522
|
+
lib.xl_abi_version.argtypes = []
|
|
523
|
+
lib.xl_abi_version.restype = c_int
|
|
524
|
+
|
|
525
|
+
lib.xl_open_write_handle.argtypes = [
|
|
526
|
+
ctypes.POINTER(ctypes.c_uint8), ctypes.c_int32, ctypes.c_int32,
|
|
527
|
+
ctypes.POINTER(NativeWriteOptions), ctypes.POINTER(ctypes.c_void_p),
|
|
528
|
+
]
|
|
529
|
+
lib.xl_open_write_handle.restype = ctypes.c_int32
|
|
530
|
+
|
|
531
|
+
lib.xl_open_write_handle_to_memory.argtypes = [
|
|
532
|
+
ctypes.c_int32, ctypes.POINTER(NativeWriteOptions), ctypes.POINTER(ctypes.c_void_p),
|
|
533
|
+
]
|
|
534
|
+
lib.xl_open_write_handle_to_memory.restype = ctypes.c_int32
|
|
535
|
+
|
|
536
|
+
lib.xl_start_sheet.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint8), ctypes.c_int32]
|
|
537
|
+
lib.xl_start_sheet.restype = ctypes.c_int32
|
|
538
|
+
|
|
539
|
+
lib.xl_start_row.argtypes = [ctypes.c_void_p]
|
|
540
|
+
lib.xl_start_row.restype = ctypes.c_int32
|
|
541
|
+
|
|
542
|
+
lib.xl_write_string.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint8), ctypes.c_int32]
|
|
543
|
+
lib.xl_write_string.restype = ctypes.c_int32
|
|
544
|
+
|
|
545
|
+
lib.xl_write_int64.argtypes = [ctypes.c_void_p, ctypes.c_int64]
|
|
546
|
+
lib.xl_write_int64.restype = ctypes.c_int32
|
|
547
|
+
|
|
548
|
+
lib.xl_write_float64.argtypes = [ctypes.c_void_p, ctypes.c_double]
|
|
549
|
+
lib.xl_write_float64.restype = ctypes.c_int32
|
|
550
|
+
|
|
551
|
+
lib.xl_write_bool.argtypes = [ctypes.c_void_p, ctypes.c_int32]
|
|
552
|
+
lib.xl_write_bool.restype = ctypes.c_int32
|
|
553
|
+
|
|
554
|
+
lib.xl_write_date.argtypes = [ctypes.c_void_p, ctypes.c_int32]
|
|
555
|
+
lib.xl_write_date.restype = ctypes.c_int32
|
|
556
|
+
|
|
557
|
+
lib.xl_write_time.argtypes = [ctypes.c_void_p, ctypes.c_int64]
|
|
558
|
+
lib.xl_write_time.restype = ctypes.c_int32
|
|
559
|
+
|
|
560
|
+
lib.xl_write_timestamp.argtypes = [ctypes.c_void_p, ctypes.c_int64]
|
|
561
|
+
lib.xl_write_timestamp.restype = ctypes.c_int32
|
|
562
|
+
|
|
563
|
+
lib.xl_write_null.argtypes = [ctypes.c_void_p, ctypes.c_int32]
|
|
564
|
+
lib.xl_write_null.restype = ctypes.c_int32
|
|
565
|
+
|
|
566
|
+
lib.xl_end_row.argtypes = [ctypes.c_void_p]
|
|
567
|
+
lib.xl_end_row.restype = ctypes.c_int32
|
|
568
|
+
|
|
569
|
+
lib.xl_end_sheet.argtypes = [ctypes.c_void_p]
|
|
570
|
+
lib.xl_end_sheet.restype = ctypes.c_int32
|
|
571
|
+
|
|
572
|
+
lib.xl_close_write_handle.argtypes = [ctypes.c_void_p]
|
|
573
|
+
lib.xl_close_write_handle.restype = ctypes.c_int32
|
|
574
|
+
|
|
575
|
+
lib.xl_write_handle_bytes.argtypes = [ctypes.c_void_p, ctypes.POINTER(NativeBuffer)]
|
|
576
|
+
lib.xl_write_handle_bytes.restype = ctypes.c_int32
|
|
577
|
+
|
|
578
|
+
lib.xl_free_buffer.argtypes = [ctypes.POINTER(NativeBuffer)]
|
|
579
|
+
lib.xl_free_buffer.restype = None
|
|
580
|
+
|
|
581
|
+
return lib
|